<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta http-equiv="Cache-Control" content="no-siteapp">
  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
  <meta name="renderer" content="webkit">

  
  <title>Android 14 Launcher 数据库及Workspace的数据加载与绑定（三） | 龙之叶的博客</title>

  <link rel="shortcut icon" href="/images/favicon.png">
  <link rel="alternate" href="/atom.xml" title="龙之叶的博客" type="application/atom+xml">
  <meta name="description" content="Android 14 Launcher 数据库及Workspace的数据加载与绑定（三）">
<meta property="og:type" content="article">
<meta property="og:title" content="Android 14 Launcher 数据库及Workspace的数据加载与绑定（三）">
<meta property="og:url" content="http://longzhiye.top/2024/02/17/2024-02-17/index.html">
<meta property="og:site_name" content="龙之叶的博客">
<meta property="og:description" content="Android 14 Launcher 数据库及Workspace的数据加载与绑定（三）">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2024-02-16T16:00:00.000Z">
<meta property="article:modified_time" content="2024-08-29T02:40:34.924Z">
<meta property="article:author" content="龙之叶">
<meta property="article:tag" content="Android">
<meta property="article:tag" content="framework">
<meta property="article:tag" content="Launcher">
<meta name="twitter:card" content="summary">

  <meta name="keywords" content=",Android,framework,Launcher">
  <meta name="format-detection" content="telephone=no,email=no">
  <meta name="theme-color" content="#9C27B0">

  <meta name="mobile-web-app-capable" content="yes">
  <meta name="application-name" content="龙之叶的博客">
  <meta name="msapplication-starturl" content="http://longzhiye.top/2024/02/17/2024-02-17/">
  <meta name="msapplication-navbutton-color" content="#9C27B0">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-title" content="龙之叶的博客">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <link rel="apple-touch-icon" href="/images/favicon.png">

  
    <link rel="canonical" href="http://longzhiye.top/2024/02/17/2024-02-17/">
  

  
  

  
  
  

  
<link rel="stylesheet" href="/css/mdui.css">
<link rel="stylesheet" href="/css/style.css">

  
<link rel="stylesheet" href="/custom.css">

<meta name="generator" content="Hexo 5.4.2"></head>

<body class="mdui-appbar-with-toolbar mdui-theme-primary-indigo mdui-theme-accent-pink">
  <script>var a=localStorage.getItem("mdui-theme-layout-dark");if(a){document.getElementsByTagName("body")[0].className+=" mdui-theme-layout-dark"};</script>
  <script>if(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches){document.getElementsByTagName("body")[0].className+=" mdui-theme-layout-dark"};</script>
  <script>var a=localStorage.getItem("mdui-drawer-close");if(!a){document.getElementsByTagName("body")[0].className+=" mdui-drawer-body-left"};</script>
  <header id="header" class="mdui-appbar mdui-appbar-fixed mdui-appbar-scroll-hide mdui-appbar-inset">
  <div class="mdui-toolbar mdui-color-theme">
    <a href="javascript:;" class="mdui-btn mdui-btn-icon" mdui-drawer="{target: '#sidebar', swipe: true}"><i class="mdui-icon material-icons">menu</i></a>
    <a href="/" class="mdui-typo-headline">龙之叶的博客</a>
    <div class="mdui-toolbar-spacer"></div>
    <a href="javascript:;" class="mdui-btn mdui-btn-icon" mdui-dialog="{target: '#search'}" mdui-tooltip="{content: '搜索'}"><i class="mdui-icon material-icons">search</i></a>
	<!--
    <a href="/atom.xml" class="mdui-btn mdui-btn-icon" mdui-tooltip="{content: 'RSS'}"><i class="mdui-icon material-icons">rss_feed</i></a>
	-->
  </div>
</header>
<div class="mdui-dialog" id="search">
  
    <div class="search-form">
      <input type="search" class="search-form-input" placeholder="请输入关键字">
    </div>
    <div class="search-result" data-resource="/search.xml"></div>
  
</div>

  <aside id="sidebar" class="mdui-drawer mdui-drawer-full-height">
  <script>var a=localStorage.getItem("mdui-drawer-close");if(a){document.getElementById("sidebar").className+=" mdui-drawer-close"};</script>
  <div class="mdui-grid-tile">
    <img src="/images/banner.png" style="height: 160px;">
    <img src="/images/avatar.png" class="avatar-animation" style="position: absolute; top: 10%; left: 24px; width: 64px; height: 64px; border: 2px solid #fff; border-radius: 50%;background-color:#fff;">
    <div class="mdui-grid-tile-actions">
      <div class="mdui-grid-tile-text">
        <div class="mdui-grid-tile-title">龙之叶</div>
        <div class="mdui-grid-tile-subtitle"><i class="mdui-icon material-icons">art_track</i>生命不息，折腾不止</div>
      </div>
      
        <div class="mdui-grid-tile-buttons">
          <a href="mailto:longzhiye163@163.com" class="mdui-btn mdui-btn-icon" mdui-tooltip="{content: 'longzhiye163@163.com'}"><i class="mdui-icon material-icons">email</i></a>
        </div>
      
    </div>
  </div>

  <div class="mdui-list" mdui-collapse="{accordion: true}">
    <a href="/" class="mdui-list-item mdui-ripple">
      <i class="mdui-list-item-icon mdui-icon material-icons mdui-text-color-blue">home</i>
      <div class="mdui-list-item-content">主页</div>
    </a>
    <div class="mdui-collapse-item">
      <script>var a=localStorage.getItem("mdui-collapse-item-0");if(a){document.getElementsByClassName("mdui-collapse-item")[0].className+=" mdui-collapse-item-open"};</script>
      <div class="mdui-collapse-item-header mdui-list-item mdui-ripple">
        <i class="mdui-list-item-icon mdui-icon material-icons mdui-text-color-deep-orange">archive</i>
        <div class="mdui-list-item-content">归档</div>
        <i class="mdui-collapse-item-arrow mdui-icon material-icons">keyboard_arrow_down</i>
      </div>
      <div class="mdui-collapse-item-body mdui-list mdui-list-dense">
        
        <a class="mdui-ripple sidebar_archives-link" href="/archives/2025/07/">七月 2025<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2025/06/">六月 2025<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2025/03/">三月 2025<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2025/02/">二月 2025<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2025/01/">一月 2025<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2024/10/">十月 2024<span class="mdui-ripple sidebar_archives-count">7</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2024/09/">九月 2024<span class="mdui-ripple sidebar_archives-count">4</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2024/08/">八月 2024<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2024/03/">三月 2024<span class="mdui-ripple sidebar_archives-count">5</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2024/02/">二月 2024<span class="mdui-ripple sidebar_archives-count">11</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2024/01/">一月 2024<span class="mdui-ripple sidebar_archives-count">9</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/12/">十二月 2023<span class="mdui-ripple sidebar_archives-count">10</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/11/">十一月 2023<span class="mdui-ripple sidebar_archives-count">8</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/10/">十月 2023<span class="mdui-ripple sidebar_archives-count">8</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/09/">九月 2023<span class="mdui-ripple sidebar_archives-count">3</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/08/">八月 2023<span class="mdui-ripple sidebar_archives-count">6</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/06/">六月 2023<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/05/">五月 2023<span class="mdui-ripple sidebar_archives-count">4</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/04/">四月 2023<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/03/">三月 2023<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2023/02/">二月 2023<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2022/09/">九月 2022<span class="mdui-ripple sidebar_archives-count">7</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2022/08/">八月 2022<span class="mdui-ripple sidebar_archives-count">4</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2017/04/">四月 2017<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2017/03/">三月 2017<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2017/02/">二月 2017<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2017/01/">一月 2017<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2016/12/">十二月 2016<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2016/11/">十一月 2016<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2016/10/">十月 2016<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/archives/2016/08/">八月 2016<span class="mdui-ripple sidebar_archives-count">1</span></a>
        
      </div>
    </div>
    <div class="mdui-collapse-item">
      <script>var a=localStorage.getItem("mdui-collapse-item-1");if(a){document.getElementsByClassName("mdui-collapse-item")[1].className+=" mdui-collapse-item-open"};</script>
      <div class="mdui-collapse-item-header mdui-list-item mdui-ripple">
        <i class="mdui-list-item-icon mdui-icon material-icons mdui-text-color-green">class</i>
        <div class="mdui-list-item-content">分类</div>
        <i class="mdui-collapse-item-arrow mdui-icon material-icons">keyboard_arrow_down</i>
      </div>
      <div class="mdui-collapse-item-body mdui-list mdui-list-dense">
        
        <a class="mdui-ripple sidebar_archives-link" href="/categories/%E6%8A%80%E6%9C%AF/">技术<span class="mdui-ripple sidebar_archives-count">98</span></a><a class="mdui-ripple sidebar_archives-link" href="/categories/%E7%94%9F%E6%B4%BB/">生活<span class="mdui-ripple sidebar_archives-count">3</span></a><a class="mdui-ripple sidebar_archives-link" href="/categories/%E8%B5%84%E8%AE%AF/">资讯<span class="mdui-ripple sidebar_archives-count">4</span></a><a class="mdui-ripple sidebar_archives-link" href="/categories/%E9%80%9A%E7%9F%A5/">通知<span class="mdui-ripple sidebar_archives-count">4</span></a>
        
      </div>
    </div>
    <div class="mdui-collapse-item">
      <script>var a=localStorage.getItem("mdui-collapse-item-2");if(a){document.getElementsByClassName("mdui-collapse-item")[2].className+=" mdui-collapse-item-open"};</script>
      <div class="mdui-collapse-item-header mdui-list-item mdui-ripple">
        <i class="mdui-list-item-icon mdui-icon material-icons mdui-text-color-brown">bookmark</i>
        <div class="mdui-list-item-content">标签</div>
        <i class="mdui-collapse-item-arrow mdui-icon material-icons">keyboard_arrow_down</i>
      </div>
      <div class="mdui-collapse-item-body mdui-list mdui-list-dense">
        
        <a class="mdui-ripple sidebar_archives-link" href="/tags/ANR/" rel="tag">ANR<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/APP/" rel="tag">APP<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Android/" rel="tag">Android<span class="mdui-ripple sidebar_archives-count">89</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Android12/" rel="tag">Android12<span class="mdui-ripple sidebar_archives-count">3</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Android13/" rel="tag">Android13<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Android14/" rel="tag">Android14<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/AndroidStudio/" rel="tag">AndroidStudio<span class="mdui-ripple sidebar_archives-count">3</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/AutoDraw/" rel="tag">AutoDraw<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Crash/" rel="tag">Crash<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Foxmail/" rel="tag">Foxmail<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Framework/" rel="tag">Framework<span class="mdui-ripple sidebar_archives-count">20</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/GPS%E8%BD%A8%E8%BF%B9%E5%9B%BE/" rel="tag">GPS轨迹图<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Git/" rel="tag">Git<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/GitHub/" rel="tag">GitHub<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Google/" rel="tag">Google<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Hexo/" rel="tag">Hexo<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Java/" rel="tag">Java<span class="mdui-ripple sidebar_archives-count">3</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Kernel/" rel="tag">Kernel<span class="mdui-ripple sidebar_archives-count">4</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Launcher/" rel="tag">Launcher<span class="mdui-ripple sidebar_archives-count">17</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Lib/" rel="tag">Lib<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Linux/" rel="tag">Linux<span class="mdui-ripple sidebar_archives-count">18</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Markdown/" rel="tag">Markdown<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Material-Design/" rel="tag">Material Design<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/OS-X/" rel="tag">OS X<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/OTA/" rel="tag">OTA<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/QuickTime/" rel="tag">QuickTime<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/SSH/" rel="tag">SSH<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Settings/" rel="tag">Settings<span class="mdui-ripple sidebar_archives-count">6</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/SystemUI/" rel="tag">SystemUI<span class="mdui-ripple sidebar_archives-count">19</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Ubuntu/" rel="tag">Ubuntu<span class="mdui-ripple sidebar_archives-count">7</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/WPS/" rel="tag">WPS<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Wifi/" rel="tag">Wifi<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/WindTerm/" rel="tag">WindTerm<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/Windows/" rel="tag">Windows<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/androidmk/" rel="tag">androidmk<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/blog/" rel="tag">blog<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/build/" rel="tag">build<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/feel/" rel="tag">feel<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/framework/" rel="tag">framework<span class="mdui-ripple sidebar_archives-count">50</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/git/" rel="tag">git<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/inotify/" rel="tag">inotify<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/life/" rel="tag">life<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/ninja/" rel="tag">ninja<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/rsync/" rel="tag">rsync<span class="mdui-ripple sidebar_archives-count">2</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/standard/" rel="tag">standard<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/test/" rel="tag">test<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1/" rel="tag">企业微信<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/" rel="tag">操作系统<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/%E6%B5%8B%E8%AF%95/" rel="tag">测试<span class="mdui-ripple sidebar_archives-count">1</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/%E6%BA%90%E7%A0%81/" rel="tag">源码<span class="mdui-ripple sidebar_archives-count">3</span></a><a class="mdui-ripple sidebar_archives-link" href="/tags/%E7%94%9F%E6%B4%BB%E6%84%9F%E6%82%9F/" rel="tag">生活感悟<span class="mdui-ripple sidebar_archives-count">1</span></a>
        
      </div>
    </div>
    <a href="/about" class="mdui-list-item mdui-ripple">
      <i class="mdui-list-item-icon mdui-icon material-icons mdui-text-color-purple">person</i>
      <div class="mdui-list-item-content">关于</div>
    </a>
  </div>

  <div class="mdui-divider"></div>

  <div class="mdui-list" mdui-collapse="{accordion: true}">
    
      <a href="/timeline" class="mdui-list-item mdui-ripple">时间轴</a>
    
    <div class="mdui-collapse-item">
      <script>var a=localStorage.getItem("mdui-collapse-item-3");if(a){document.getElementsByClassName("mdui-collapse-item")[3].className+=" mdui-collapse-item-open"};</script>
      <div class="mdui-collapse-item-header mdui-list-item mdui-ripple">
        <div class="mdui-list-item-content">友情链接</div>
        <i class="mdui-list-item-icon mdui-icon material-icons">link</i>
      </div>
      <div class="mdui-collapse-item-body mdui-list mdui-list-dense">
        
          <a href="http://www.niemingzhao.top" target="_blank" class="mdui-list-item mdui-p-l-2 mdui-text-color-theme-accent mdui-ripple" style="justify-content: center;">聂明照的博客</a>
        
        
      </div>
    </div>
  </div>
</aside>

  <main id="main" class="mdui-m-t-5 fadeIn animated">
  
<link rel="stylesheet" href="//cdn.bootcdn.net/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css">

  <style>#main article .mdui-card-content .center-block{display:block!important;margin-right:auto!important;margin-left:auto!important}</style>
  <style>#main article .mdui-card-content .text-center{text-align:center!important}</style>
  <article class="mdui-card mdui-m-b-5">
    <header class="mdui-card-media">
      <img src="/images/random/material-16.png" style="max-height: 240px;">
      <div class="mdui-card-media-covered">
        <div class="mdui-card-primary">
          <div class="mdui-card-primary-title">Android 14 Launcher 数据库及Workspace的数据加载与绑定（三）</div>
          <div class="mdui-card-primary-subtitle"><i class="iconfont">&#xe697;</i> 2024-02-17 / <i class="iconfont">&#xe601;</i> 龙之叶 &nbsp;&nbsp; <span id="busuanzi_container_page_pv" style="display: none;"><i class="iconfont">&#xe7fd;</i> <span id="busuanzi_value_page_pv"></span></span></div>
        </div>
      </div>
      <div class="mdui-card-menu">
        
          <button class="mdui-btn mdui-btn-icon mdui-text-color-white" mdui-menu="{target: '#qrcode', align: 'right'}"><i class="mdui-icon material-icons">devices</i></button>
          <ul class="mdui-menu" id="qrcode">
            
              <li class="mdui-menu-item"><a class="mdui-text-center mdui-ripple">发送到手机</a></li>
            
            <li class="mdui-menu-item" disabled>
              
                <img src="//qr.liantu.com/api.php?w=246&m=10&text=http://longzhiye.top/2024/02/17/2024-02-17/">
              
            </li>
          </ul>
        
        
          <button class="mdui-btn mdui-btn-icon mdui-text-color-white" mdui-menu="{target: '#share_menu', align: 'right'}"><i class="mdui-icon material-icons">share</i></button>
          <ul class="mdui-menu" id="share_menu">
            <li class="mdui-menu-item">
              <a href="https://service.weibo.com/share/share.php?appkey=&title=Android 14 Launcher 数据库及Workspace的数据加载与绑定（三）&url=http://longzhiye.top/2024/02/17/2024-02-17/&pic=http://longzhiye.top/images/favicon.png&searchPic=false&style=simple" target="_blank" class="mdui-ripple">分享到 Weibo</a>
            </li>
            <li class="mdui-menu-item">
              <a href="https://twitter.com/intent/tweet?text=Android 14 Launcher 数据库及Workspace的数据加载与绑定（三）&url=http://longzhiye.top/2024/02/17/2024-02-17/&via=龙之叶" target="_blank" class="mdui-ripple">分享到 Twitter</a>
            </li>
            <li class="mdui-menu-item">
              <a href="https://www.facebook.com/sharer/sharer.php?u=http://longzhiye.top/2024/02/17/2024-02-17/" target="_blank" class="mdui-ripple">分享到 Facebook</a>
            </li>
            <li class="mdui-menu-item">
              <a href="https://plus.google.com/share?url=http://longzhiye.top/2024/02/17/2024-02-17/" target="_blank" class="mdui-ripple">分享到 Google+</a>
            </li>
            <li class="mdui-menu-item">
              <a href="https://www.linkedin.com/shareArticle?mini=true&url=http://longzhiye.top/2024/02/17/2024-02-17/&title=Android 14 Launcher 数据库及Workspace的数据加载与绑定（三）" target="_blank" class="mdui-ripple">分享到 LinkedIn</a>
            </li>
            <li class="mdui-menu-item">
              <a href="https://connect.qq.com/widget/shareqq/index.html?site=龙之叶的博客&title=Android 14 Launcher 数据库及Workspace的数据加载与绑定（三）&pics=http://longzhiye.top/images/favicon.png&url=http://longzhiye.top/2024/02/17/2024-02-17/" target="_blank" class="mdui-ripple">分享到 QQ</a>
            </li>
            <li class="mdui-menu-item">
              <a href="https://telegram.me/share/url?url=http://longzhiye.top/2024/02/17/2024-02-17/&text=Android 14 Launcher 数据库及Workspace的数据加载与绑定（三）" target="_blank" class="mdui-ripple">分享到 Telegram</a>
            </li>
          </ul>
        
      </div>
    </header>
    <div class="mdui-card-content mdui-typo">
      <h3 id="1-Workspace-介绍"><a href="#1-Workspace-介绍" class="headerlink" title="1. Workspace 介绍"></a>1. Workspace 介绍</h3><p>在 Android 手机上，我们通常说的桌面其实就是 <strong>launcher</strong> ，再往小了说就是： <strong>Workspace</strong> 。 <strong>Workspace</strong> 是桌面在实现时的抽象定义。桌面上显示的应用图标、文件夹和小部件都是显示在 <strong>Workspace</strong> 中的，我们可以增删应用快捷图标，增删文件夹，增删小部件。</p>
<p>在手机重启或关机后 <strong>Workspace</strong> 中这么多 <strong>Widget</strong> 的状态怎么保存呢？</p>
<blockquote>
<p>答案是：<strong>launcher 使用了一个专门的数据库保存了这些 Widget 的状态，以便下次重启后依然能按照最新的变动显示。</strong></p>
</blockquote>
<p>下面从 <strong>launcher.db</strong> <strong>数据库创建</strong> 、 <strong>Workspace</strong> 数据加载这两点展开分析。</p>
<h3 id="2-launcher-db-数据库创建"><a href="#2-launcher-db-数据库创建" class="headerlink" title="2. launcher.db 数据库创建"></a>2. launcher.db 数据库创建</h3><p><strong>launcher.db</strong> 的创建得从 <strong>LauncherProvider</strong> 展开，在该类中可以看到 <strong>LauncherProvider#createDbIfNotExists()</strong> 方法：<br><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">createDbIfNotExists</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mOpenHelper == <span class="literal">null</span>) &#123;</span><br><span class="line">        mOpenHelper = DatabaseHelper.createDatabaseHelper(</span><br><span class="line">                getContext(), <span class="literal">false</span> <span class="comment">/* forMigration */</span>);</span><br><span class="line"></span><br><span class="line">        RestoreDbTask.restoreIfNeeded(getContext(), mOpenHelper);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在整个 Launcher 只有这一个位置实例化了 <strong>DatabaseHelper</strong> ,而且在对数据库进行操作时都会调用到 <strong>LauncherProvider#createDbIfNotExists()</strong> .<br>接着看 <strong>LauncherProvider.DatabaseHelper#createDatabaseHelper()</strong> :</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> DatabaseHelper <span class="title function_">createDatabaseHelper</span><span class="params">(Context context, <span class="type">boolean</span> forMigration)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> createDatabaseHelper(context, <span class="literal">null</span>, forMigration);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> DatabaseHelper <span class="title function_">createDatabaseHelper</span><span class="params">(Context context, String dbName,</span></span><br><span class="line"><span class="params">        <span class="type">boolean</span> forMigration)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (dbName == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// dbName 为 launcher.db</span></span><br><span class="line">        dbName = InvariantDeviceProfile.INSTANCE.get(context).dbFile;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 创建数据库</span></span><br><span class="line">    <span class="type">DatabaseHelper</span> <span class="variable">databaseHelper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DatabaseHelper</span>(context, dbName, forMigration);</span><br><span class="line">    <span class="comment">// 表创建有时会无提示地失败，从而导致崩溃循环。这样，我们将在每次崩溃后尝试创建这个表，以便设备最终能够恢复。</span></span><br><span class="line">    <span class="keyword">if</span> (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) &#123;</span><br><span class="line">        <span class="comment">// 调用 onCreate 后表丢失。试图重建.</span></span><br><span class="line">        <span class="comment">// 如果表已经存在，则此操作是空操作。</span></span><br><span class="line">        databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), <span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    databaseHelper.mHotseatRestoreTableExists = tableExists(</span><br><span class="line">            databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);</span><br><span class="line"></span><br><span class="line">    databaseHelper.initIds();</span><br><span class="line">    <span class="keyword">return</span> databaseHelper;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>到此数据库就创建完成了，接下来就是建表。<br><strong>LauncherProvider.DatabaseHelper#onCreate()</strong> :</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">(SQLiteDatabase db)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (LOGD) Log.d(TAG, <span class="string">&quot;creating new launcher database&quot;</span>);</span><br><span class="line"></span><br><span class="line">    mMaxItemId = <span class="number">1</span>;</span><br><span class="line">    <span class="comment">// 建表，addFavoritesTable() 方法后面那个参数表示：表是否存在，true 为不存在</span></span><br><span class="line">    addFavoritesTable(db, <span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Fresh and clean launcher DB.</span></span><br><span class="line">    mMaxItemId = initializeMaxItemId(db);</span><br><span class="line">    <span class="keyword">if</span> (!mForMigration) &#123;</span><br><span class="line">        <span class="comment">// 这个方法值得注意下</span></span><br><span class="line">        onEmptyDbCreated();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onEmptyDbCreated</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// Set the flag for empty DB</span></span><br><span class="line">    Utilities.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), <span class="literal">true</span>)</span><br><span class="line">            .commit();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>实际建表操作在 <strong>LauncherProvider.DatabaseHelper#onCreate()</strong> 方法里，但在 <strong>LauncherProvider.DatabaseHelper#createDatabaseHelper()</strong> 里也有个同样得建表操作，注意这里：是不会重复建表得，有相应得判断。</p>
<p><strong>onEmptyDbCreated()</strong> 方法中记录了一个 <strong>EMPTY_DATABASE_CREATED</strong> 标记，表示空数据库创建了。该标记在 <strong>loadWorkspace</strong> 时， <strong>loadDefaultFavoritesIfNecessary</strong> 方法用到了此标记：</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">synchronized</span> <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">loadDefaultFavoritesIfNecessary</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">SharedPreferences</span> <span class="variable">sp</span> <span class="operator">=</span> Utilities.getPrefs(getContext());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), <span class="literal">false</span>)) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 省略部分代码......</span></span><br><span class="line">        clearFlagEmptyDbCreated();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">clearFlagEmptyDbCreated</span><span class="params">()</span> &#123;</span><br><span class="line">    Utilities.getPrefs(getContext()).edit()</span><br><span class="line">            .remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里使用这个标记判断是否需要加载默认的 <strong>workspace</strong> 配置数据到数据库，最后一行代码 <strong>clearFlagEmptyDbCreated()</strong> 方法调用，用于清空了这个标记，下次就不需要再次加载了。</p>
<p>从中得出一个结论， <strong>launcher正常在首次加载时，才会加载默认配置到数据库，其他情况是不会加载的</strong> 。</p>
<h3 id="3-Workspace-数据加载"><a href="#3-Workspace-数据加载" class="headerlink" title="3. Workspace 数据加载"></a>3. Workspace 数据加载</h3><p><strong>Workspace</strong> 的数据加载在 <strong>LoaderTask#loadWorkspace()</strong> 方法开始的。</p>
<p><strong>LoaderTask#loadWorkspace()</strong> :</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/model/LoaderTask.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">loadWorkspace</span><span class="params">(</span></span><br><span class="line"><span class="params">        List&lt;ShortcutInfo&gt; allDeepShortcuts,</span></span><br><span class="line"><span class="params">        Uri contentUri,</span></span><br><span class="line"><span class="params">        String selection,</span></span><br><span class="line"><span class="params">        <span class="meta">@Nullable</span> LoaderMemoryLogger logger)</span> &#123;</span><br><span class="line">    <span class="comment">// 首先是创建了一些对象，这些对象，在Launcher启动流程之前大多都已经创建过，这里是获取实例</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">Context</span> <span class="variable">context</span> <span class="operator">=</span> mApp.getContext();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ContentResolver</span> <span class="variable">contentResolver</span> <span class="operator">=</span> context.getContentResolver();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">PackageManagerHelper</span> <span class="variable">pmHelper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PackageManagerHelper</span>(context);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">isSafeMode</span> <span class="operator">=</span> pmHelper.isSafeMode();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">isSdCardReady</span> <span class="operator">=</span> Utilities.isBootCompleted();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">WidgetManagerHelper</span> <span class="variable">widgetHelper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">WidgetManagerHelper</span>(context);</span><br><span class="line"></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">clearDb</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">if</span> (!GridSizeMigrationTaskV2.migrateGridIfNeeded(context)) &#123;</span><br><span class="line">        <span class="comment">// 迁移失败。清除工作区。</span></span><br><span class="line">        clearDb = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 这一分支基本走不到</span></span><br><span class="line">    <span class="keyword">if</span> (clearDb) &#123;</span><br><span class="line">        <span class="comment">// 重新启动数据库</span></span><br><span class="line">        LauncherSettings.Settings.call(contentResolver,</span><br><span class="line">                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 重要位置 ********** 1 *********加载布局 </span></span><br><span class="line">    <span class="comment">// 这个一定会执行</span></span><br><span class="line">    <span class="comment">// LauncherSettings.Settings.call() 方法的实现在 LauncherProvider 中。</span></span><br><span class="line">    <span class="comment">// 该方法加载了布局。</span></span><br><span class="line">    Log.d(TAG, <span class="string">&quot;loadWorkspace: loading default favorites&quot;</span>);</span><br><span class="line">    LauncherSettings.Settings.call(contentResolver,</span><br><span class="line">            LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重要位置 ********** 2 ********* 获取数据库信息 ，下面会有分析</span></span><br><span class="line">    <span class="comment">// 省略部分代码......</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>上述代码分为两个重点位置</strong>：</p>
<ol>
<li>加载布局</li>
<li>获取数据库信息</li>
</ol>
<h4 id="1-先看第一点：加载布局"><a href="#1-先看第一点：加载布局" class="headerlink" title="1. 先看第一点：加载布局"></a>1. 先看第一点：加载布局</h4><blockquote>
<p>注意：<strong>LauncherProvider#call()</strong> 方法这里就补贴出来了，自己去看。</p>
</blockquote>
<p>上述 <strong>LauncherSettings.Settings.call()</strong> 方法的实现在 <strong>LauncherProvider</strong> 中，该方法是：读取布局的方法，桌面布局有默认布局和自定义布局。默认布局是在首次开机，恢复出厂设置，清空桌面数据的时候；<strong>Launcher</strong>运行期间会把桌面布局存在数据库里，而开机时会去读取数据库，根据数据库来决定布局。</p>
<p><strong>LauncherProvider#call()</strong> 方法每次执行时，都会执行 <strong>createDbIfNotExists()</strong> 检查是否有数据库，如果没有则创建一次数据库。<br>即如果数据库为空就会创建数据库；实际使用时，在首次开机，恢复出厂设置，清空桌面数据的时候数据库为空，这种情况下就会创建一个空的数据库。</p>
<p><strong>LauncherProvider#createDbIfNotExists()</strong> :</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">createDbIfNotExists</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mOpenHelper == <span class="literal">null</span>) &#123;</span><br><span class="line">        mOpenHelper = DatabaseHelper.createDatabaseHelper(</span><br><span class="line">                getContext(), <span class="literal">false</span> <span class="comment">/* forMigration */</span>);</span><br><span class="line"></span><br><span class="line">        RestoreDbTask.restoreIfNeeded(getContext(), mOpenHelper);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> DatabaseHelper <span class="title function_">createDatabaseHelper</span><span class="params">(Context context, String dbName,</span></span><br><span class="line"><span class="params">        <span class="type">boolean</span> forMigration)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略部分代码......</span></span><br><span class="line">    <span class="keyword">if</span> (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 创建两个table表，图标和屏幕：addFavoritesTable,addWorkspacesTable</span></span><br><span class="line">        <span class="comment">// 注：Android 14源码只有这一个表，没用屏幕表。</span></span><br><span class="line">        databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), <span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 省略部分代码......</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>根据上述代码接着看 <strong>LauncherProvider#addFavoritesTable()</strong> ：</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">addFavoritesTable</span><span class="params">(SQLiteDatabase db, <span class="type">boolean</span> optional)</span> &#123;</span><br><span class="line">    <span class="comment">// 这里将会调用到   LauncherSettings.java</span></span><br><span class="line">    Favorites.addTableToDb(db, getDefaultUserSerial(), optional);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// LauncherSettings.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">addTableToDb</span><span class="params">(SQLiteDatabase db, <span class="type">long</span> myProfileId, <span class="type">boolean</span> optional)</span> &#123;</span><br><span class="line">    addTableToDb(db, myProfileId, optional, TABLE_NAME);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// LauncherSettings.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">addTableToDb</span><span class="params">(SQLiteDatabase db, <span class="type">long</span> myProfileId, <span class="type">boolean</span> optional,</span></span><br><span class="line"><span class="params">        String tableName)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">ifNotExists</span> <span class="operator">=</span> optional ? <span class="string">&quot; IF NOT EXISTS &quot;</span> : <span class="string">&quot;&quot;</span>;</span><br><span class="line">    db.execSQL(<span class="string">&quot;CREATE TABLE &quot;</span> + ifNotExists + tableName + <span class="string">&quot; (&quot;</span> +</span><br><span class="line">            <span class="string">&quot;_id INTEGER PRIMARY KEY,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;title TEXT,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;intent TEXT,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;container INTEGER,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;screen INTEGER,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;cellX INTEGER,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;cellY INTEGER,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;spanX INTEGER,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;spanY INTEGER,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;itemType INTEGER,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;appWidgetId INTEGER NOT NULL DEFAULT -1,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;iconPackage TEXT,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;iconResource TEXT,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;icon BLOB,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;appWidgetProvider TEXT,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;modified INTEGER NOT NULL DEFAULT 0,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;restored INTEGER NOT NULL DEFAULT 0,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;profileId INTEGER DEFAULT &quot;</span> + myProfileId + <span class="string">&quot;,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;rank INTEGER NOT NULL DEFAULT 0,&quot;</span> +</span><br><span class="line">            <span class="string">&quot;options INTEGER NOT NULL DEFAULT 0,&quot;</span> +</span><br><span class="line">            APPWIDGET_SOURCE + <span class="string">&quot; INTEGER NOT NULL DEFAULT &quot;</span> + CONTAINER_UNKNOWN +</span><br><span class="line">            <span class="string">&quot;);&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里解释一些重要数据库的含义：</p>
<ul>
<li><strong>Container</strong>：判断属于当前图标属于哪里：包括文件夹、workspace 和 hotseat。其中如果图标属于文件夹则，图标的 container 值就是其 id 值。</li>
<li><strong>Intent</strong>：点击的时候启动的目标。</li>
<li><strong>cellX和cellY</strong>：图标起始于第几行第几列。</li>
<li><strong>spanX和spanY</strong>：widget占据格子数。</li>
<li><strong>itemType</strong>：区分具体类型。类型包括，图标，文件夹，widget等</li>
</ul>
<p>在 <strong>loadWorkspace()</strong> 的开始实际进行的第一个操作是：判断是否有桌面布局数据库，从而好读取数据。如果没有用户布局数据则采用 <strong>loadDefaultFavoritesIfNecessary()</strong> 方法。实际上没有用户布局数据的场景就是第一次创建数据库的场景。所以 <strong>loadDefaultFavoritesIfNecessary()</strong> 的含义是读取默认布局，仅在首次开机，恢复出厂设置或清除 <strong>Launcher</strong> 数据的时候使用。</p>
<p>接着看 <strong>LauncherProvider#loadDefaultFavoritesIfNecessary()</strong> :</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">synchronized</span> <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">loadDefaultFavoritesIfNecessary</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">SharedPreferences</span> <span class="variable">sp</span> <span class="operator">=</span> Utilities.getPrefs(getContext());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), <span class="literal">false</span>)) &#123;</span><br><span class="line">        Log.d(TAG, <span class="string">&quot;loading default workspace&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">AppWidgetHost</span> <span class="variable">widgetHost</span> <span class="operator">=</span> mOpenHelper.newLauncherWidgetHost();</span><br><span class="line">            <span class="comment">// 获取布局，</span></span><br><span class="line">        <span class="type">AutoInstallsLayout</span> <span class="variable">loader</span> <span class="operator">=</span> createWorkspaceLoaderFromAppRestriction(widgetHost);</span><br><span class="line">        <span class="keyword">if</span> (loader == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 获取布局，下面分析 AutoInstallsLayout </span></span><br><span class="line">            loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (loader == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">Partner</span> <span class="variable">partner</span> <span class="operator">=</span> Partner.get(getContext().getPackageManager());</span><br><span class="line">            <span class="keyword">if</span> (partner != <span class="literal">null</span> &amp;&amp; partner.hasDefaultLayout()) &#123;</span><br><span class="line">                <span class="keyword">final</span> <span class="type">Resources</span> <span class="variable">partnerRes</span> <span class="operator">=</span> partner.getResources();</span><br><span class="line">                <span class="type">int</span> <span class="variable">workspaceResId</span> <span class="operator">=</span> partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,</span><br><span class="line">                        <span class="string">&quot;xml&quot;</span>, partner.getPackageName());</span><br><span class="line">                <span class="keyword">if</span> (workspaceResId != <span class="number">0</span>) &#123;</span><br><span class="line">                    loader = <span class="keyword">new</span> <span class="title class_">DefaultLayoutParser</span>(getContext(), mOpenHelper.mAppWidgetHost,</span><br><span class="line">                            mOpenHelper, partnerRes, workspaceResId);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">usingExternallyProvidedLayout</span> <span class="operator">=</span> loader != <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">if</span> (loader == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 获取布局</span></span><br><span class="line">            loader = getDefaultLayoutParser(widgetHost);</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 创一个数据库</span></span><br><span class="line">        mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());</span><br><span class="line">        <span class="comment">// xml文件的内容解析并放入数据库；没理解错，就是把：xml布局文件放到数据库中，重点在 loadFavorites()</span></span><br><span class="line">        <span class="keyword">if</span> ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) &lt;= <span class="number">0</span>)</span><br><span class="line">                &amp;&amp; usingExternallyProvidedLayout) &#123;</span><br><span class="line">            <span class="comment">// Unable to load external layout. Cleanup and load the internal layout.</span></span><br><span class="line">            mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());</span><br><span class="line">            mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),</span><br><span class="line">                    getDefaultLayoutParser(widgetHost));</span><br><span class="line">        &#125;</span><br><span class="line">        clearFlagEmptyDbCreated();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>通过上面代码可知： <strong>loadDefaultFavoritesIfNecessary()</strong> 方法的作用为：获取 loader (布局)，和将读取的布局存入数据库。</p>
<p>获取 <strong>AutoInstallsLayout</strong> 方法，首先获取 <strong>layoutName</strong> ，这个名字就是xml名字。在原生代码 <strong>res/xml/</strong> 文件夹下面有 <strong>default_workspace.xml 、default_workspace_3x3.xml、 default_workspace_4x4.xml、default_workspace_5x5.xml、default_workspace_5x6.xml</strong> 一共5个布局文件。</p>
<p>下面则是采用 <strong>多个方式</strong> 来获取布局 xml，因为不知道 xml 文件的具体名字所以采用递进的方法来获取。</p>
<p><strong>先看第一种</strong>：应用约束，调用 <strong>createWorkspaceLoaderFromAppRestriction()</strong> ，获取用户设置的一组用于限制应用功能的 Bundle 串，获取 Bundle 里 <strong>workspace.configuration.package.name</strong> 具体的应用包名，获取 WorkSpace 默认配置资源。 <strong>LauncherProvider#createWorkspaceLoaderFromAppRestriction(widgetHost)</strong> :</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> AutoInstallsLayout <span class="title function_">createWorkspaceLoaderFromAppRestriction</span><span class="params">(AppWidgetHost widgetHost)</span> &#123;</span><br><span class="line">    <span class="type">Context</span> <span class="variable">ctx</span> <span class="operator">=</span> getContext();</span><br><span class="line">    <span class="keyword">final</span> String authority;</span><br><span class="line">    <span class="keyword">if</span> (!TextUtils.isEmpty(mProviderAuthority)) &#123;</span><br><span class="line">        authority = mProviderAuthority;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        authority = Settings.Secure.getString(ctx.getContentResolver(),</span><br><span class="line">                <span class="string">&quot;launcher3.layout.provider&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (TextUtils.isEmpty(authority)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">ProviderInfo</span> <span class="variable">pi</span> <span class="operator">=</span> ctx.getPackageManager().resolveContentProvider(authority, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (pi == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 找不到权限的提供者</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获取布局 Uri</span></span><br><span class="line">    <span class="type">Uri</span> <span class="variable">uri</span> <span class="operator">=</span> getLayoutUri(authority, ctx);</span><br><span class="line">    <span class="keyword">try</span> (<span class="type">InputStream</span> <span class="variable">in</span> <span class="operator">=</span> ctx.getContentResolver().openInputStream(uri)) &#123;</span><br><span class="line">        <span class="comment">// 阅读完整的 xml，以便在出现任何 IO 错误时尽早失败</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">layout</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(IOUtils.toByteArray(in));</span><br><span class="line">        <span class="type">XmlPullParser</span> <span class="variable">parser</span> <span class="operator">=</span> Xml.newPullParser();</span><br><span class="line">        parser.setInput(<span class="keyword">new</span> <span class="title class_">StringReader</span>(layout));</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AutoInstallsLayout</span>(ctx, widgetHost, mOpenHelper,</span><br><span class="line">                ctx.getPackageManager().getResourcesForApplication(pi.applicationInfo),</span><br><span class="line">                () -&gt; parser, AutoInstallsLayout.TAG_WORKSPACE);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        Log.e(TAG, <span class="string">&quot;Error getting layout stream from: &quot;</span> + authority , e);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>再看第二种</strong>：从 intent 关键字 <strong>ACTION_LAUNCHER_CUSTOMIZATION</strong> 即是 <strong>“android.autoinstalls.config.action.PLAY_AUTO_INSTALL”</strong> 来获取，autoinstall 可以在手机中集成对应工具，这样默认布局除了手机自带的应用外，还可以提供一些自动下载的应用。</p>
<p><strong>AutoInstallsLayout#get()</strong> :<br><strong>packages/apps/Launcher3/src/com/android/launcher3/AutoInstallsLayout.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> AutoInstallsLayout <span class="title function_">get</span><span class="params">(Context context, AppWidgetHost appWidgetHost,</span></span><br><span class="line"><span class="params">        LayoutParserCallback callback)</span> &#123;</span><br><span class="line">    Pair&lt;String, Resources&gt; customizationApkInfo = PackageManagerHelper.findSystemApk(</span><br><span class="line">            ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager());</span><br><span class="line">    <span class="keyword">if</span> (customizationApkInfo == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">String</span> <span class="variable">pkg</span> <span class="operator">=</span> customizationApkInfo.first;</span><br><span class="line">    <span class="type">Resources</span> <span class="variable">targetRes</span> <span class="operator">=</span> customizationApkInfo.second;</span><br><span class="line">    <span class="type">InvariantDeviceProfile</span> <span class="variable">grid</span> <span class="operator">=</span> LauncherAppState.getIDP(context);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这里得到的布局名字为：default_layout_%dx%d_h%s </span></span><br><span class="line">    <span class="type">String</span> <span class="variable">layoutName</span> <span class="operator">=</span> String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,</span><br><span class="line">            grid.numColumns, grid.numRows, grid.numDatabaseHotseatIcons);</span><br><span class="line">    <span class="type">int</span> <span class="variable">layoutId</span> <span class="operator">=</span> targetRes.getIdentifier(layoutName, <span class="string">&quot;xml&quot;</span>, pkg);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这里得到的布局名字为：default_layout_%dx%d</span></span><br><span class="line">    <span class="keyword">if</span> (layoutId == <span class="number">0</span>) &#123;</span><br><span class="line">        Log.d(TAG, <span class="string">&quot;Formatted layout: &quot;</span> + layoutName</span><br><span class="line">                + <span class="string">&quot; not found. Trying layout without hosteat&quot;</span>);</span><br><span class="line">        layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,</span><br><span class="line">                grid.numColumns, grid.numRows);</span><br><span class="line">        layoutId = targetRes.getIdentifier(layoutName, <span class="string">&quot;xml&quot;</span>, pkg);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这里得到的布局名字为：default_layout</span></span><br><span class="line">    <span class="keyword">if</span> (layoutId == <span class="number">0</span>) &#123;</span><br><span class="line">        Log.d(TAG, <span class="string">&quot;Formatted layout: &quot;</span> + layoutName + <span class="string">&quot; not found. Trying the default layout&quot;</span>);</span><br><span class="line">        layoutId = targetRes.getIdentifier(LAYOUT_RES, <span class="string">&quot;xml&quot;</span>, pkg);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (layoutId == <span class="number">0</span>) &#123;</span><br><span class="line">        Log.e(TAG, <span class="string">&quot;Layout definition not found in package: &quot;</span> + pkg);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 把有关信息保存在AutoInstallsLayout，返回给调用的程序.</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AutoInstallsLayout</span>(context, appWidgetHost, callback, targetRes, layoutId,</span><br><span class="line">            TAG_WORKSPACE);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>总之： <strong>AutoInstallsLayout.get() 根据传入的参数，读取对应的xml文件</strong>。</p>
<p><strong>再看第三种</strong>：从系统内置的 partner 应用里获取workspace默认配置。 这种就不过多介绍了。</p>
<p><strong>看第四种</strong>：是最常用的一种，我们能控制的本地布局，调用 getDefaultLayoutParser() 获取我们 Launcher 里的默认资源。</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> DefaultLayoutParser <span class="title function_">getDefaultLayoutParser</span><span class="params">(AppWidgetHost widgetHost)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">defaultLayout</span> <span class="operator">=</span> LauncherAppState.getIDP(getContext()).defaultLayoutId;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DefaultLayoutParser</span>(getContext(), widgetHost,</span><br><span class="line">            mOpenHelper, getContext().getResources(), defaultLayout);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherAppState.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> InvariantDeviceProfile <span class="title function_">getIDP</span><span class="params">(Context context)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> LauncherAppState.getInstance(context).getInvariantDeviceProfile();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>loadDefaultFavoritesIfNecessary()</strong> 方法又分为：读取布局、存储布局。</p>
<p>存储布局的主要方法是： <strong>loadFavorites()</strong> ，由于文章过于长了，这里就不在作分析了。</p>
<h4 id="2-获取数据库信息"><a href="#2-获取数据库信息" class="headerlink" title="2. 获取数据库信息"></a>2. 获取数据库信息</h4><p>回到开始的 <strong>LoaderTask#loadWorkspace()</strong> 方法。</p>
<blockquote>
<p>该类剩下部分的代码还是非常多，后面将拆开分析。</p>
</blockquote>
<p><strong>LoaderTask#loadWorkspace()</strong><br><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">loadWorkspace</span><span class="params">(</span></span><br><span class="line"><span class="params">        List&lt;ShortcutInfo&gt; allDeepShortcuts,</span></span><br><span class="line"><span class="params">        Uri contentUri,</span></span><br><span class="line"><span class="params">        String selection,</span></span><br><span class="line"><span class="params">        <span class="meta">@Nullable</span> LoaderMemoryLogger logger)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略部门代码......</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">synchronized</span> (mBgDataModel) &#123;</span><br><span class="line">        mBgDataModel.clear();</span><br><span class="line">        mPendingPackages.clear();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">final</span> HashMap&lt;PackageUserKey, SessionInfo&gt; installingPkgs =</span><br><span class="line">                mSessionHelper.getActiveSessions();</span><br><span class="line">        installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">final</span> <span class="type">PackageUserKey</span> <span class="variable">tempPackageKey</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PackageUserKey</span>(<span class="literal">null</span>, <span class="literal">null</span>);</span><br><span class="line">        mFirstScreenBroadcast = <span class="keyword">new</span> <span class="title class_">FirstScreenBroadcast</span>(installingPkgs);</span><br><span class="line"></span><br><span class="line">        Map&lt;ShortcutKey, ShortcutInfo&gt; shortcutKeyToPinnedShortcuts = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 重点关注 ****** LoaderCursor() *******</span></span><br><span class="line">        <span class="keyword">final</span> <span class="type">LoaderCursor</span> <span class="variable">c</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LoaderCursor</span>(</span><br><span class="line">                contentResolver.query(contentUri, <span class="literal">null</span>, selection, <span class="literal">null</span>, <span class="literal">null</span>), contentUri,</span><br><span class="line">                mApp, mUserManagerState);</span><br><span class="line">        <span class="keyword">final</span> <span class="type">Bundle</span> <span class="variable">extras</span> <span class="operator">=</span> c.getExtras();</span><br><span class="line">        mDbName = extras == <span class="literal">null</span></span><br><span class="line">                ? <span class="literal">null</span> : extras.getString(LauncherSettings.Settings.EXTRA_DB_NAME);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 这下面是补充一些需要获取的参数，这些对象会反复使用</span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">appWidgetIdIndex</span> <span class="operator">=</span> c.getColumnIndexOrThrow(</span><br><span class="line">                    LauncherSettings.Favorites.APPWIDGET_ID);</span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">appWidgetProviderIndex</span> <span class="operator">=</span> c.getColumnIndexOrThrow(</span><br><span class="line">                    LauncherSettings.Favorites.APPWIDGET_PROVIDER);</span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">spanXIndex</span> <span class="operator">=</span> c.getColumnIndexOrThrow</span><br><span class="line">                    (LauncherSettings.Favorites.SPANX);</span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">spanYIndex</span> <span class="operator">=</span> c.getColumnIndexOrThrow(</span><br><span class="line">                    LauncherSettings.Favorites.SPANY);</span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">rankIndex</span> <span class="operator">=</span> c.getColumnIndexOrThrow(</span><br><span class="line">                    LauncherSettings.Favorites.RANK);</span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">optionsIndex</span> <span class="operator">=</span> c.getColumnIndexOrThrow(</span><br><span class="line">                    LauncherSettings.Favorites.OPTIONS);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 省略部门代码......</span></span><br><span class="line">        &#125;</span><br><span class="line">&#125;</span><br><span class="line">```        </span><br><span class="line"></span><br><span class="line">上述代码创建了 **LoaderCursor** 游标，用于暂时存储从数据库中提取的数据块，且创建是根据 table 名字来获取对应的数据库 **table**， 这里的名字是 **Favorites**。</span><br><span class="line">接着看下 **LoaderCursor** 的构造方法： **LoaderCursor#LoaderCursor()**</span><br><span class="line"></span><br><span class="line">**packages/apps/Launcher3/src/com/android/launcher3/model/LoaderCursor.java**</span><br><span class="line">```java</span><br><span class="line"><span class="keyword">public</span> <span class="title function_">LoaderCursor</span><span class="params">(Cursor cursor, Uri contentUri, LauncherAppState app,</span></span><br><span class="line"><span class="params">        UserManagerState userManagerState)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(cursor);</span><br><span class="line"></span><br><span class="line">    allUsers = userManagerState.allUsers;</span><br><span class="line">    mContentUri = contentUri;</span><br><span class="line">    mContext = app.getContext();</span><br><span class="line">    mIconCache = app.getIconCache();</span><br><span class="line">    mIDP = app.getInvariantDeviceProfile();</span><br><span class="line">    mPM = mContext.getPackageManager();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化列索引</span></span><br><span class="line">    iconIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);</span><br><span class="line">    iconPackageIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);</span><br><span class="line">    iconResourceIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);</span><br><span class="line">    titleIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);</span><br><span class="line"></span><br><span class="line">    idIndex = getColumnIndexOrThrow(LauncherSettings.Favorites._ID);</span><br><span class="line">    containerIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);</span><br><span class="line">    itemTypeIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);</span><br><span class="line">    screenIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);</span><br><span class="line">    cellXIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);</span><br><span class="line">    cellYIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);</span><br><span class="line">    profileIdIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE_ID);</span><br><span class="line">    restoredIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.RESTORED);</span><br><span class="line">    intentIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>整个构造器，定义了数据库中的所有词条，后面则使用这些词条来获取相应参数。</p>
<p>回到 <strong>loadWorkspace()</strong> ，看后面的部分。<br><strong>LoaderTask#loadWorkspace()</strong><br><strong>packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">loadWorkspace</span><span class="params">(</span></span><br><span class="line"><span class="params">        List&lt;ShortcutInfo&gt; allDeepShortcuts,</span></span><br><span class="line"><span class="params">        Uri contentUri,</span></span><br><span class="line"><span class="params">        String selection,</span></span><br><span class="line"><span class="params">        <span class="meta">@Nullable</span> LoaderMemoryLogger logger)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略部门代码......</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">synchronized</span> (mBgDataModel) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">while</span> (!mStopped &amp;&amp; c.moveToNext()) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span> (c.user == <span class="literal">null</span>) &#123;</span><br><span class="line">                        <span class="comment">// 用户已被删除，删除该 item.</span></span><br><span class="line">                        c.markDeleted(<span class="string">&quot;User has been deleted&quot;</span>);</span><br><span class="line">                        <span class="keyword">continue</span>;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="type">boolean</span> <span class="variable">allowMissingTarget</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">                    <span class="comment">// 对数据库每一条的读取方式，按照类型区分，</span></span><br><span class="line">                    <span class="comment">// 最常见的是图标类型，SHORTCUT、APPLICATION、DEEP_SHORTCUT都是图标类型。</span></span><br><span class="line">                    <span class="comment">// 图标类型，在桌面上占据1x1的格子，且点击打开对应应用的属于图标大类。</span></span><br><span class="line">                    <span class="keyword">switch</span> (c.itemType) &#123;</span><br><span class="line">                    <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:</span><br><span class="line">                    <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:</span><br><span class="line">                    <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:</span><br><span class="line">                        <span class="comment">// 下面这句代码是从 c 获取 intent</span></span><br><span class="line">                        <span class="comment">// intent 参数来源有三处。一个是xml文件中，在首次开机的时候；</span></span><br><span class="line">                        <span class="comment">// 一个是packagemanager，手机里面安装的应用的intent 都是知道的；</span></span><br><span class="line">                        <span class="comment">// 最后是快捷方式生成的intent。 Intent是用来启动应用的参数。</span></span><br><span class="line">                        intent = c.parseIntent();</span><br><span class="line">                        <span class="keyword">if</span> (intent == <span class="literal">null</span>) &#123;</span><br><span class="line">                            c.markDeleted(<span class="string">&quot;Invalid or null intent&quot;</span>);</span><br><span class="line">                            <span class="keyword">continue</span>;</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        <span class="type">int</span> <span class="variable">disabledState</span> <span class="operator">=</span> mUserManagerState.isUserQuiet(c.serialNumber)</span><br><span class="line">                                ? WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER : <span class="number">0</span>;</span><br><span class="line">                        <span class="type">ComponentName</span> <span class="variable">cn</span> <span class="operator">=</span> intent.getComponent();</span><br><span class="line">                        targetPkg = cn == <span class="literal">null</span> ? intent.getPackage() : cn.getPackageName();</span><br><span class="line">                        <span class="comment">// 检查是否有对应的package name，如果没有传入包名则不是应用</span></span><br><span class="line">                        <span class="keyword">if</span> (TextUtils.isEmpty(targetPkg) &amp;&amp;</span><br><span class="line">                                c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &#123;</span><br><span class="line">                            c.markDeleted(<span class="string">&quot;Only legacy shortcuts can have null package&quot;</span>);</span><br><span class="line">                            <span class="keyword">continue</span>;</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">                        <span class="type">boolean</span> <span class="variable">validTarget</span> <span class="operator">=</span> TextUtils.isEmpty(targetPkg) ||</span><br><span class="line">                                mLauncherApps.isPackageEnabled(targetPkg, c.user);</span><br><span class="line"></span><br><span class="line">                        </span><br><span class="line">                        <span class="keyword">if</span> (cn != <span class="literal">null</span> &amp;&amp; validTarget &amp;&amp; c.itemType</span><br><span class="line">                                != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &#123;</span><br><span class="line">                            <span class="comment">// 检查对应的应用是否在系统中为disable状态，如果为disable状态，则不显示。</span></span><br><span class="line">                            <span class="comment">// 通过 isActivityEnabled() 来判断。 当用户在设置里面对某个应用设置为 disable，回到 Launcher 的时候，Launche r的数据库里面还是保留着该应用。</span></span><br><span class="line">                            <span class="comment">// 这里会进行一个判断，当数据库有，但手机不支持的时候，不显示</span></span><br><span class="line">                            <span class="keyword">if</span> (mLauncherApps.isActivityEnabled(cn, c.user)) &#123;</span><br><span class="line">    </span><br><span class="line">                                c.markRestored();</span><br><span class="line">                            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                <span class="comment">// Gracefully try to find a fallback activity.</span></span><br><span class="line">                                intent = pmHelper.getAppLaunchIntent(targetPkg, c.user);</span><br><span class="line">                                <span class="keyword">if</span> (intent != <span class="literal">null</span>) &#123;</span><br><span class="line">                                    c.restoreFlag = <span class="number">0</span>;</span><br><span class="line">                                    c.updater().put(</span><br><span class="line">                                            LauncherSettings.Favorites.INTENT,</span><br><span class="line">                                            intent.toUri(<span class="number">0</span>)).commit();</span><br><span class="line">                                    cn = intent.getComponent();</span><br><span class="line">                                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                    c.markDeleted(<span class="string">&quot;Unable to find a launch target&quot;</span>);</span><br><span class="line">                                    <span class="keyword">continue</span>;</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                        </span><br><span class="line"></span><br><span class="line">                        <span class="keyword">if</span> (!TextUtils.isEmpty(targetPkg) &amp;&amp; !validTarget) &#123;</span><br><span class="line">                            <span class="comment">// 指向一个有效的应用程序（ cn != null），但该应用程序不可用</span></span><br><span class="line">                            <span class="keyword">if</span> (c.restoreFlag != <span class="number">0</span>) &#123;</span><br><span class="line">                                <span class="comment">// 软件包尚不可用，但稍后可能会安装。这种是显示在桌面上的                      </span></span><br><span class="line">                                tempPackageKey.update(targetPkg, c.user);</span><br><span class="line">                                <span class="keyword">if</span> (c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED)) &#123;</span><br><span class="line">                                    <span class="comment">// 恢复已开始一次</span></span><br><span class="line">                                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (installingPkgs.containsKey(tempPackageKey)) &#123;</span><br><span class="line">                                    <span class="comment">// 应用恢复已开始。更新标志</span></span><br><span class="line">                                    c.restoreFlag |= WorkspaceItemInfo.FLAG_RESTORE_STARTED;</span><br><span class="line">                                    c.updater().put(LauncherSettings.Favorites.RESTORED,</span><br><span class="line">                                            c.restoreFlag).commit();</span><br><span class="line">                                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                    <span class="comment">// 未恢复的应用程序已删除</span></span><br><span class="line">                                    c.markDeleted(<span class="string">&quot;Unrestored app removed: &quot;</span> + targetPkg);</span><br><span class="line">                                    <span class="keyword">continue</span>;</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pmHelper.isAppOnSdcard(targetPkg, c.user)) &#123;</span><br><span class="line">                                <span class="comment">// 应用安装到手机，桌面上也放置了，但是应用安装在了SD卡里面，而此时此刻SD尚未读取完成。</span></span><br><span class="line">                                <span class="comment">// 这个时候仍然把图标放置到桌面上。</span></span><br><span class="line">                                <span class="comment">// 判断时，明确应用是安装在SD卡里，且SD卡没有读取到</span></span><br><span class="line"></span><br><span class="line">                                <span class="comment">// Package 存在但不可用</span></span><br><span class="line">                                disabledState |= WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE;</span><br><span class="line">                                <span class="comment">// 在 workspace 中添加图标 .</span></span><br><span class="line">                                allowMissingTarget = <span class="literal">true</span>;</span><br><span class="line">                            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!isSdCardReady) &#123;</span><br><span class="line">                                <span class="comment">// SdCard 还没有准备好。一旦准备就绪，包可能会可用。缺少 pkg时，将延迟检查</span></span><br><span class="line">                </span><br><span class="line">                                mPendingPackages.add(<span class="keyword">new</span> <span class="title class_">PackageUserKey</span>(targetPkg, c.user));</span><br><span class="line">                                <span class="comment">// 在 workspace 中添加图标 .</span></span><br><span class="line">                                allowMissingTarget = <span class="literal">true</span>;</span><br><span class="line">                            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                <span class="comment">// 不再等待外部加载。</span></span><br><span class="line">                                c.markDeleted(<span class="string">&quot;Invalid package removed: &quot;</span> + targetPkg);</span><br><span class="line">                                <span class="keyword">continue</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        <span class="keyword">if</span> ((c.restoreFlag &amp; WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI) != <span class="number">0</span>) &#123;</span><br><span class="line">                            validTarget = <span class="literal">false</span>;</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        <span class="keyword">if</span> (validTarget) &#123;</span><br><span class="line">                            <span class="comment">// The shortcut points to a valid target (either no target</span></span><br><span class="line">                            <span class="comment">// or something which is ready to be used)</span></span><br><span class="line">                            c.markRestored();</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="comment">// 部分图标在读取的时候采用低分辨率图标来提高读取速度。</span></span><br><span class="line">                        <span class="comment">// 区分方式是，用户是否能很快看到图标。</span></span><br><span class="line">                        <span class="comment">// Launcher 将文件夹中、不在文件夹小图标预览的应用设为低分辨率。</span></span><br><span class="line">                        <span class="type">boolean</span> <span class="variable">useLowResIcon</span> <span class="operator">=</span> !c.isOnWorkspaceOrHotseat();</span><br><span class="line">                        <span class="comment">// 不同的图标细节不同。</span></span><br><span class="line">                        <span class="comment">// SHORTCUT 是独立的快捷方式</span></span><br><span class="line">                        <span class="comment">// DEEP_SHORTCUT 是依托于应用的快捷方式，</span></span><br><span class="line">                        <span class="comment">// 而 APPLICATION 就是应用。</span></span><br><span class="line">                        <span class="keyword">if</span> (c.restoreFlag != <span class="number">0</span>) &#123;</span><br><span class="line">                            <span class="comment">// Already verified above that user is same as default user</span></span><br><span class="line">                            info = c.getRestoredItemInfo(intent);</span><br><span class="line">                        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (c.itemType ==</span><br><span class="line">                                LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) &#123;</span><br><span class="line">                            <span class="comment">// 当itemtype是application的时候，会调用getAppShortcutInfo()，</span></span><br><span class="line">                            <span class="comment">// 在其中获取应用需要的数据存储在 shortcutinfo中，</span></span><br><span class="line">                            <span class="comment">// 这里生成的shortcutinfo对象具备一个在桌面上显示的快捷方式所需的一切资源，</span></span><br><span class="line">                            <span class="comment">// 比如名称，图标，点击后打开的intent等</span></span><br><span class="line">                            <span class="comment">// ******重要****getAppShortcutInfo() **********</span></span><br><span class="line">                            info = c.getAppShortcutInfo(</span><br><span class="line">                                    intent,</span><br><span class="line">                                    allowMissingTarget,</span><br><span class="line">                                    useLowResIcon,</span><br><span class="line">                                    !FeatureFlags.ENABLE_BULK_WORKSPACE_ICON_LOADING.get());</span><br><span class="line">                        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (c.itemType ==</span><br><span class="line">                                LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &#123;</span><br><span class="line">                            <span class="comment">// deep shortcut 和 application 是不一样的，</span></span><br><span class="line">                            <span class="comment">// deepshortcut 是和 systemservise 通过储存的快捷方式，手机在生成 deepshort 的时候，deepshortcut 点击所打开的对象是保存在手机里（不是Launcher里），同时传递一个id给Launcher，Launcher只保存id，</span></span><br><span class="line">                            <span class="comment">// 当用户点击 deepshortcut 的时候，Launcher用过id想手机申请打开id对应的目标对象。</span></span><br><span class="line">                            <span class="comment">// 这是新平台才有的功能。 此外，和application不同，deepshortcut 的图标是Launcher提供的。</span></span><br><span class="line"></span><br><span class="line">                            <span class="type">ShortcutKey</span> <span class="variable">key</span> <span class="operator">=</span> ShortcutKey.fromIntent(intent, c.user);</span><br><span class="line">                            <span class="keyword">if</span> (unlockedUsers.get(c.serialNumber)) &#123;</span><br><span class="line">                                <span class="type">ShortcutInfo</span> <span class="variable">pinnedShortcut</span> <span class="operator">=</span></span><br><span class="line">                                        shortcutKeyToPinnedShortcuts.get(key);</span><br><span class="line">                                <span class="keyword">if</span> (pinnedShortcut == <span class="literal">null</span>) &#123;</span><br><span class="line">                                    <span class="comment">// 快捷方式不再有效。</span></span><br><span class="line">                                    c.markDeleted(<span class="string">&quot;Pinned shortcut not found&quot;</span>);</span><br><span class="line">                                    <span class="keyword">continue</span>;</span><br><span class="line">                                &#125;</span><br><span class="line">                                info = <span class="keyword">new</span> <span class="title class_">WorkspaceItemInfo</span>(pinnedShortcut, context);</span><br><span class="line">                                <span class="comment">// 如果不再发布 deep shortcut 快捷方式，请使用上次保存的图标，而不是默认图标</span></span><br><span class="line">                                mIconCache.getShortcutIcon(info, pinnedShortcut, c::loadIcon);</span><br><span class="line"></span><br><span class="line">                                <span class="keyword">if</span> (pmHelper.isAppSuspended(</span><br><span class="line">                                        pinnedShortcut.getPackage(), info.user)) &#123;</span><br><span class="line">                                    info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;</span><br><span class="line">                                &#125;</span><br><span class="line">                                intent = info.getIntent();</span><br><span class="line">                                allDeepShortcuts.add(pinnedShortcut);</span><br><span class="line">                            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                <span class="comment">// 现在在禁用模式下创建快捷方式信息。</span></span><br><span class="line">                                info = c.loadSimpleWorkspaceItem();</span><br><span class="line">                                info.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123; <span class="comment">// item type == ITEM_TYPE_SHORTCUT</span></span><br><span class="line">                            info = c.loadSimpleWorkspaceItem();</span><br><span class="line"></span><br><span class="line">                            <span class="comment">// 快捷方式仅适用于主要配置文件</span></span><br><span class="line">                            <span class="keyword">if</span> (!TextUtils.isEmpty(targetPkg)</span><br><span class="line">                                    &amp;&amp; pmHelper.isAppSuspended(targetPkg, c.user)) &#123;</span><br><span class="line">                                disabledState |= FLAG_DISABLED_SUSPENDED;</span><br><span class="line">                            &#125;</span><br><span class="line">                            info.options = c.getInt(optionsIndex);</span><br><span class="line"></span><br><span class="line">                            <span class="keyword">if</span> (intent.getAction() != <span class="literal">null</span> &amp;&amp;</span><br><span class="line">                                intent.getCategories() != <span class="literal">null</span> &amp;&amp;</span><br><span class="line">                                intent.getAction().equals(Intent.ACTION_MAIN) &amp;&amp;</span><br><span class="line">                                intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) &#123;</span><br><span class="line">                                intent.addFlags(</span><br><span class="line">                                    Intent.FLAG_ACTIVITY_NEW_TASK |</span><br><span class="line">                                    Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        <span class="keyword">if</span> (info != <span class="literal">null</span>) &#123;</span><br><span class="line">                            <span class="keyword">if</span> (info.itemType</span><br><span class="line">                                    != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &#123;</span><br><span class="line">                                <span class="comment">// 跳过 deep shortcuts；他们的标题和图标已经在上面加载了。</span></span><br><span class="line">                                iconRequestInfos.add(</span><br><span class="line">                                        c.createIconRequestInfo(info, useLowResIcon));</span><br><span class="line">                            &#125;</span><br><span class="line"></span><br><span class="line">                            c.applyCommonProperties(info);</span><br><span class="line">                            <span class="comment">// 快捷方式的 spanX 和 spanY 默认是1，</span></span><br><span class="line">                            <span class="comment">// 则直接取一，intent则是从数据库里面获取的。</span></span><br><span class="line">                            info.intent = intent;</span><br><span class="line">                            info.rank = c.getInt(rankIndex);</span><br><span class="line">                            info.spanX = <span class="number">1</span>;</span><br><span class="line">                            info.spanY = <span class="number">1</span>;</span><br><span class="line">                            info.runtimeStatusFlags |= disabledState;</span><br><span class="line">                            <span class="keyword">if</span> (isSafeMode &amp;&amp; !isSystemApp(context, intent)) &#123;</span><br><span class="line">                                info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE;</span><br><span class="line">                            &#125;</span><br><span class="line">                                <span class="type">LauncherActivityInfo</span> <span class="variable">activityInfo</span> <span class="operator">=</span> c.getLauncherActivityInfo();</span><br><span class="line">                                <span class="keyword">if</span> (activityInfo != <span class="literal">null</span>) &#123;</span><br><span class="line">                                    info.setProgressLevel(</span><br><span class="line">                                            PackageManagerHelper</span><br><span class="line">                                                .getLoadingProgress(activityInfo),</span><br><span class="line">                                            PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);</span><br><span class="line">                                &#125;</span><br><span class="line"></span><br><span class="line">                            <span class="keyword">if</span> (c.restoreFlag != <span class="number">0</span> &amp;&amp; !TextUtils.isEmpty(targetPkg)) &#123;</span><br><span class="line">                                tempPackageKey.update(targetPkg, c.user);</span><br><span class="line">                                <span class="type">SessionInfo</span> <span class="variable">si</span> <span class="operator">=</span> installingPkgs.get(tempPackageKey);</span><br><span class="line">                                    <span class="keyword">if</span> (si == <span class="literal">null</span>) &#123;</span><br><span class="line">                                        info.runtimeStatusFlags &amp;=</span><br><span class="line">                                            ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;</span><br><span class="line">                                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (activityInfo == <span class="literal">null</span>) &#123;</span><br><span class="line">                                        <span class="type">int</span> <span class="variable">installProgress</span> <span class="operator">=</span> (<span class="type">int</span>) (si.getProgress() * <span class="number">100</span>);</span><br><span class="line"></span><br><span class="line">                                        info.setProgressLevel(</span><br><span class="line">                                                installProgress,</span><br><span class="line">                                                PackageInstallInfo.STATUS_INSTALLING);</span><br><span class="line">                                    &#125;</span><br><span class="line">                            &#125;</span><br><span class="line">                            <span class="comment">// 最终将数据存入缓存sBgDataModel中</span></span><br><span class="line">                            c.checkAndAddItem(info, mBgDataModel, logger);</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Unexpected null WorkspaceItemInfo&quot;</span>);</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    <span class="comment">// 文件夹数据类型是创建一个空的文件夹，文件夹不打开其他应用没有intent，</span></span><br><span class="line">                    <span class="comment">// 文件夹的名称title是区分文件夹的要素之一。</span></span><br><span class="line">                    <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_FOLDER:</span><br><span class="line">                        <span class="type">FolderInfo</span> <span class="variable">folderInfo</span> <span class="operator">=</span> mBgDataModel.findOrMakeFolder(c.id);</span><br><span class="line">                        c.applyCommonProperties(folderInfo);</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// 不要修剪文件夹标签，因为它是由用户设置的。</span></span><br><span class="line">                        folderInfo.title = c.getString(c.titleIndex);</span><br><span class="line">                        folderInfo.spanX = <span class="number">1</span>;</span><br><span class="line">                        folderInfo.spanY = <span class="number">1</span>;</span><br><span class="line">                        folderInfo.options = c.getInt(optionsIndex);</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// 恢复的文件夹不需要特殊处理</span></span><br><span class="line">                        c.markRestored();</span><br><span class="line">                        <span class="comment">// 文件夹也是放入缓存sBgDataModel中，桌面能显示的都要放在sBgDataModel中</span></span><br><span class="line">                        c.checkAndAddItem(folderInfo, mBgDataModel, logger);</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// widget是需要设置spanX和spanY的，也只有widget才可能占两格以上。</span></span><br><span class="line">                        <span class="comment">// 同时，由于每个widget的显示内容都是由第三方的应用实时控制，所以在判断上比较繁琐。</span></span><br><span class="line">                    <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:</span><br><span class="line">                        <span class="keyword">if</span> (WidgetsModel.GO_DISABLE_WIDGETS) &#123;</span><br><span class="line">                            c.markDeleted(<span class="string">&quot;Only legacy shortcuts can have null package&quot;</span>);</span><br><span class="line">                            <span class="keyword">continue</span>;</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="comment">// Follow through</span></span><br><span class="line">                    <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:</span><br><span class="line">                        <span class="comment">// Read all Launcher-specific widget details</span></span><br><span class="line">                        <span class="type">boolean</span> <span class="variable">customWidget</span> <span class="operator">=</span> c.itemType ==</span><br><span class="line">                            LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;</span><br><span class="line"></span><br><span class="line">                        <span class="type">int</span> <span class="variable">appWidgetId</span> <span class="operator">=</span> c.getInt(appWidgetIdIndex);</span><br><span class="line">                        <span class="type">String</span> <span class="variable">savedProvider</span> <span class="operator">=</span> c.getString(appWidgetProviderIndex);</span><br><span class="line">                        <span class="keyword">final</span> ComponentName component;</span><br><span class="line"></span><br><span class="line">                        <span class="type">boolean</span> <span class="variable">isSearchWidget</span> <span class="operator">=</span> (c.getInt(optionsIndex)</span><br><span class="line">                                &amp; LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET) != <span class="number">0</span>;</span><br><span class="line">                        <span class="keyword">if</span> (isSearchWidget) &#123;</span><br><span class="line">                            component  = QsbContainerView.getSearchComponentName(context);</span><br><span class="line">                            <span class="keyword">if</span> (component == <span class="literal">null</span>) &#123;</span><br><span class="line">                                c.markDeleted(<span class="string">&quot;Discarding SearchWidget without packagename &quot;</span>);</span><br><span class="line">                                <span class="keyword">continue</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            component = ComponentName.unflattenFromString(savedProvider);</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">isIdValid</span> <span class="operator">=</span> !c.hasRestoreFlag(</span><br><span class="line">                                LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);</span><br><span class="line">                        <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">wasProviderReady</span> <span class="operator">=</span> !c.hasRestoreFlag(</span><br><span class="line">                                LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);</span><br><span class="line"></span><br><span class="line">                        <span class="type">ComponentKey</span> <span class="variable">providerKey</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ComponentKey</span>(component, c.user);</span><br><span class="line">                        <span class="keyword">if</span> (!mWidgetProvidersMap.containsKey(providerKey)) &#123;</span><br><span class="line">                            mWidgetProvidersMap.put(providerKey,</span><br><span class="line">                                    widgetHelper.findProvider(component, c.user));</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">final</span> <span class="type">AppWidgetProviderInfo</span> <span class="variable">provider</span> <span class="operator">=</span></span><br><span class="line">                                mWidgetProvidersMap.get(providerKey);</span><br><span class="line"></span><br><span class="line">                        <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">isProviderReady</span> <span class="operator">=</span> isValidProvider(provider);</span><br><span class="line">                        <span class="keyword">if</span> (!isSafeMode &amp;&amp; !customWidget &amp;&amp;</span><br><span class="line">                                wasProviderReady &amp;&amp; !isProviderReady) &#123;</span><br><span class="line">                            c.markDeleted(</span><br><span class="line">                                    <span class="string">&quot;Deleting widget that isn&#x27;t installed anymore: &quot;</span></span><br><span class="line">                                    + provider);</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            <span class="keyword">if</span> (isProviderReady) &#123;</span><br><span class="line">                                appWidgetInfo = <span class="keyword">new</span> <span class="title class_">LauncherAppWidgetInfo</span>(appWidgetId,</span><br><span class="line">                                        provider.provider);</span><br><span class="line">                                <span class="type">int</span> <span class="variable">status</span> <span class="operator">=</span> c.restoreFlag &amp;</span><br><span class="line">                                        ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED &amp;</span><br><span class="line">                                        ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;</span><br><span class="line">                                <span class="keyword">if</span> (!wasProviderReady) &#123;</span><br><span class="line">                                    <span class="keyword">if</span> (isIdValid) &#123;</span><br><span class="line">                                        status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;</span><br><span class="line">                                    &#125;</span><br><span class="line">                                &#125;</span><br><span class="line">                                appWidgetInfo.restoreStatus = status;</span><br><span class="line">                            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                Log.v(TAG, <span class="string">&quot;Widget restore pending id=&quot;</span> + c.id</span><br><span class="line">                                        + <span class="string">&quot; appWidgetId=&quot;</span> + appWidgetId</span><br><span class="line">                                        + <span class="string">&quot; status =&quot;</span> + c.restoreFlag);</span><br><span class="line">                                appWidgetInfo = <span class="keyword">new</span> <span class="title class_">LauncherAppWidgetInfo</span>(appWidgetId,</span><br><span class="line">                                        component);</span><br><span class="line">                                appWidgetInfo.restoreStatus = c.restoreFlag;</span><br><span class="line"></span><br><span class="line">                                tempPackageKey.update(component.getPackageName(), c.user);</span><br><span class="line">                                <span class="type">SessionInfo</span> <span class="variable">si</span> <span class="operator">=</span></span><br><span class="line">                                        installingPkgs.get(tempPackageKey);</span><br><span class="line">                                <span class="type">Integer</span> <span class="variable">installProgress</span> <span class="operator">=</span> si == <span class="literal">null</span></span><br><span class="line">                                        ? <span class="literal">null</span></span><br><span class="line">                                        : (<span class="type">int</span>) (si.getProgress() * <span class="number">100</span>);</span><br><span class="line"></span><br><span class="line">                                <span class="keyword">if</span> (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) &#123;</span><br><span class="line">                                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (installProgress != <span class="literal">null</span>) &#123;</span><br><span class="line">                                    appWidgetInfo.restoreStatus |=</span><br><span class="line">                                            LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;</span><br><span class="line">                                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!isSafeMode) &#123;</span><br><span class="line">                                    c.markDeleted(<span class="string">&quot;Unrestored widget removed: &quot;</span> + component);</span><br><span class="line">                                    <span class="keyword">continue</span>;</span><br><span class="line">                                &#125;</span><br><span class="line"></span><br><span class="line">                                appWidgetInfo.installProgress =</span><br><span class="line">                                        installProgress == <span class="literal">null</span> ? <span class="number">0</span> : installProgress;</span><br><span class="line">                            &#125;</span><br><span class="line">                            <span class="keyword">if</span> (appWidgetInfo.hasRestoreFlag(</span><br><span class="line">                                    LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) &#123;</span><br><span class="line">                                appWidgetInfo.bindOptions = c.parseIntent();</span><br><span class="line">                            &#125;</span><br><span class="line"></span><br><span class="line">                            c.applyCommonProperties(appWidgetInfo);</span><br><span class="line">                            appWidgetInfo.spanX = c.getInt(spanXIndex);</span><br><span class="line">                            appWidgetInfo.spanY = c.getInt(spanYIndex);</span><br><span class="line">                            appWidgetInfo.options = c.getInt(optionsIndex);</span><br><span class="line">                            appWidgetInfo.user = c.user;</span><br><span class="line">                            appWidgetInfo.sourceContainer = c.getInt(sourceContainerIndex);</span><br><span class="line"></span><br><span class="line">                            <span class="keyword">if</span> (appWidgetInfo.spanX &lt;= <span class="number">0</span> || appWidgetInfo.spanY &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">                                c.markDeleted(<span class="string">&quot;Widget has invalid size: &quot;</span></span><br><span class="line">                                        + appWidgetInfo.spanX + <span class="string">&quot;x&quot;</span> + appWidgetInfo.spanY);</span><br><span class="line">                                <span class="keyword">continue</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                            widgetProviderInfo =</span><br><span class="line">                                    widgetHelper.getLauncherAppWidgetInfo(appWidgetId);</span><br><span class="line">                            <span class="keyword">if</span> (widgetProviderInfo != <span class="literal">null</span></span><br><span class="line">                                    &amp;&amp; (appWidgetInfo.spanX &lt; widgetProviderInfo.minSpanX</span><br><span class="line">                                    || appWidgetInfo.spanY &lt; widgetProviderInfo.minSpanY)) &#123;</span><br><span class="line">                                FileLog.d(TAG, <span class="string">&quot;Widget &quot;</span> + widgetProviderInfo.getComponent()</span><br><span class="line">                                        + <span class="string">&quot; minSizes not meet: span=&quot;</span> + appWidgetInfo.spanX</span><br><span class="line">                                        + <span class="string">&quot;x&quot;</span> + appWidgetInfo.spanY + <span class="string">&quot; minSpan=&quot;</span></span><br><span class="line">                                        + widgetProviderInfo.minSpanX + <span class="string">&quot;x&quot;</span></span><br><span class="line">                                        + widgetProviderInfo.minSpanY);</span><br><span class="line">                                logWidgetInfo(mApp.getInvariantDeviceProfile(),</span><br><span class="line">                                        widgetProviderInfo);</span><br><span class="line">                            &#125;</span><br><span class="line">                            <span class="keyword">if</span> (!c.isOnWorkspaceOrHotseat()) &#123;</span><br><span class="line">                                c.markDeleted(<span class="string">&quot;Widget found where container != &quot;</span> +</span><br><span class="line">                                        <span class="string">&quot;CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!&quot;</span>);</span><br><span class="line">                                <span class="keyword">continue</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                            <span class="keyword">if</span> (!customWidget) &#123;</span><br><span class="line">                                <span class="type">String</span> <span class="variable">providerName</span> <span class="operator">=</span></span><br><span class="line">                                        appWidgetInfo.providerName.flattenToString();</span><br><span class="line">                                <span class="keyword">if</span> (!providerName.equals(savedProvider) ||</span><br><span class="line">                                        (appWidgetInfo.restoreStatus != c.restoreFlag)) &#123;</span><br><span class="line">                                    c.updater()</span><br><span class="line">                                            .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,</span><br><span class="line">                                                    providerName)</span><br><span class="line">                                            .put(LauncherSettings.Favorites.RESTORED,</span><br><span class="line">                                                    appWidgetInfo.restoreStatus)</span><br><span class="line">                                            .commit();</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;</span><br><span class="line"></span><br><span class="line">                            <span class="keyword">if</span> (appWidgetInfo.restoreStatus !=</span><br><span class="line">                                    LauncherAppWidgetInfo.RESTORE_COMPLETED) &#123;</span><br><span class="line">                                appWidgetInfo.pendingItemInfo = WidgetsModel.newPendingItemInfo(</span><br><span class="line">                                        mApp.getContext(),</span><br><span class="line">                                        appWidgetInfo.providerName,</span><br><span class="line">                                        appWidgetInfo.user);</span><br><span class="line">                                mIconCache.getTitleAndIconForApp(</span><br><span class="line">                                        appWidgetInfo.pendingItemInfo, <span class="literal">false</span>);</span><br><span class="line">                            &#125;</span><br><span class="line">                            <span class="comment">//将能够显示在桌面上的widget存放到 sBgDataModel中。</span></span><br><span class="line">                            c.checkAndAddItem(appWidgetInfo, mBgDataModel);</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    Log.e(TAG, <span class="string">&quot;Desktop items loading interrupted&quot;</span>, e);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 省略部门代码......</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// Load delegate items</span></span><br><span class="line">        mModelDelegate.loadItems(mUserManagerState, shortcutKeyToPinnedShortcuts);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Load string cache</span></span><br><span class="line">        mModelDelegate.loadStringCache(mBgDataModel.stringCache);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Break early if we&#x27;ve stopped loading</span></span><br><span class="line">        <span class="keyword">if</span> (mStopped) &#123;</span><br><span class="line">            mBgDataModel.clear();</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Remove dead items</span></span><br><span class="line">        mItemsDeleted = c.commitDeleted();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Sort the folder items, update ranks, and make sure all preview items are high res.</span></span><br><span class="line">        <span class="type">FolderGridOrganizer</span> <span class="variable">verifier</span> <span class="operator">=</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">FolderGridOrganizer</span>(mApp.getInvariantDeviceProfile());</span><br><span class="line">        <span class="keyword">for</span> (FolderInfo folder : mBgDataModel.folders) &#123;</span><br><span class="line">            Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);</span><br><span class="line">            verifier.setFolderInfo(folder);</span><br><span class="line">            <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> folder.contents.size();</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Update ranks here to ensure there are no gaps caused by removed folder items.</span></span><br><span class="line">            <span class="comment">// Ranks are the source of truth for folder items, so cellX and cellY can be ignored</span></span><br><span class="line">            <span class="comment">// for now. Database will be updated once user manually modifies folder.</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">rank</span> <span class="operator">=</span> <span class="number">0</span>; rank &lt; size; ++rank) &#123;</span><br><span class="line">                <span class="type">WorkspaceItemInfo</span> <span class="variable">info</span> <span class="operator">=</span> folder.contents.get(rank);</span><br><span class="line">                info.rank = rank;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (info.usingLowResIcon()</span><br><span class="line">                        &amp;&amp; info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION</span><br><span class="line">                        &amp;&amp; verifier.isItemInPreview(info.rank)) &#123;</span><br><span class="line">                    mIconCache.getTitleAndIcon(info, <span class="literal">false</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        c.commitRestoredItems();</span><br><span class="line">        &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码总结成：</p>
<ul>
<li>通过 LauncherSettings.Favorites.CONTENT_URI 查询 Favorites 表的所有内容，拿到cursor。</li>
<li>遍历cursor，进行数据的整理。每一行数据都有一个对应的itemType，标志着这一行的数据对应的是一个应用、还是一个Widget或文件夹等。不同的类型会进行不同的处理。</li>
<li>对于图标类型( itemType 是ITEM_TYPE_SHORTCUT，ITEM_TYPE_APPLICATION，ITEM_TYPE_DEEP_SHORTCUT)，首先经过一系列判断，判断其是否还可用(比如应用在 Launcher 未启动时被卸载导致不可用)，不可用的话就标记为可删除，继续循环。如果可用的话，就根据当前 cursor 的内容，生成一个 ShortcutInfo 对象，保存到BgDataModel。</li>
<li>对于文件夹类型(itemType是ITEM_TYPE_FOLDER)，直接生成一个对应的FolderInfo对象，保存到BgDataModel。</li>
<li>对于AppWidget(itemType是ITEM_TYPE_APPWIDGET，ITEM_TYPE_CUSTOM_APPWIDGET)，也需要经过是否可用的判断，但是可用条件与图标类型是有差异的。如果可用，生成一个LauncherAppWidgetInfo对象，保存到BgDataModel。</li>
<li>所有数据库里读出的内容已经分类完毕，并且保存到了内存(BgDataModel)中。最后开始处理之前标记为可删除的内容。显示从数据库中删除对应的行，然后还要判断此次删除操作是否带来了其他需要删除的内容。比如某个文件夹或者某一页只有一个图标，这个图标因为某些原因被删掉了，那么此文件夹或页面也需要被删掉。</li>
</ul>
<h3 id="4-Workspace-数据绑定"><a href="#4-Workspace-数据绑定" class="headerlink" title="4. Workspace 数据绑定"></a>4. Workspace 数据绑定</h3><p>这一步将 sBgDataModel 中的图标放到桌面上。 放置的时候为了提高用户体现，优先放置当前屏幕的图标和 widget，然后再放其他屏幕的图标和 widget，这样用户能更快的看到图标显示完成。<br><strong>BaseLoaderResults#bindWorkspace()</strong></p>
<p>//BaseLoaderResults.java</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bindWorkspace</span><span class="params">(<span class="type">boolean</span> incrementBindId)</span> &#123;</span><br><span class="line">    <span class="comment">// 一共创建了三个信息，屏幕数，桌面图标，桌面widget。</span></span><br><span class="line">    <span class="comment">// 后面将按照屏幕数、桌面图标、桌面widget依次绘制。</span></span><br><span class="line">    ArrayList&lt;ItemInfo&gt; workspaceItems = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    ArrayList&lt;LauncherAppWidgetInfo&gt; appWidgets = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">IntArray</span> <span class="variable">orderedScreenIds</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IntArray</span>();</span><br><span class="line">    ArrayList&lt;FixedContainerItems&gt; extraItems = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">synchronized</span> (mBgDataModel) &#123;</span><br><span class="line">        workspaceItems.addAll(mBgDataModel.workspaceItems);</span><br><span class="line">        appWidgets.addAll(mBgDataModel.appWidgets);</span><br><span class="line">        <span class="comment">// 重点关注：**** collectWorkspaceScreens() ****</span></span><br><span class="line">        <span class="comment">// 该方法做了如下操作：</span></span><br><span class="line">        <span class="comment">// 图标信息到位之后，先找到当前屏幕。</span></span><br><span class="line">        <span class="comment">// 获取屏幕的id，屏幕的id是0,1,2这个顺序，且严格按照这个顺序。</span></span><br><span class="line">        <span class="comment">// 比如Id为1，则必定是从左往右的第2个屏幕。在图标信息iteminfo里面存有每个图标的screenid信息</span></span><br><span class="line">        orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());</span><br><span class="line"></span><br><span class="line">        mBgDataModel.extraItems.forEach(extraItems::add);</span><br><span class="line">        <span class="keyword">if</span> (incrementBindId) &#123;</span><br><span class="line">            mBgDataModel.lastBindId++;</span><br><span class="line">        &#125;</span><br><span class="line">        mMyBindingId = mBgDataModel.lastBindId;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (Callbacks cb : mCallbacksList) &#123;</span><br><span class="line">        <span class="comment">// 重点关注：****** bind() *********</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">WorkspaceBinder</span>(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,</span><br><span class="line">                workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码做了两个操作：一个优先找出当前屏幕、二个绑定操作。<br>这里重点关注绑定操作 <strong>BaseLoaderResults.WorkspaceBinder#bind()</strong> :</p>
<p>// BaseLoaderResults.java</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">bind</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">IntSet</span> <span class="variable">currentScreenIds</span> <span class="operator">=</span></span><br><span class="line">            mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);</span><br><span class="line">    Objects.requireNonNull(currentScreenIds, <span class="string">&quot;Null screen ids provided by &quot;</span> + mCallbacks);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将图标分为在 当前屏幕 和 没有在当前屏幕，</span></span><br><span class="line">    <span class="comment">// 且由于widget 和其他类型的文件有巨大差异，如内容提供方和占空间大小。所以，widget和其他分为两类。</span></span><br><span class="line">    ArrayList&lt;ItemInfo&gt; currentWorkspaceItems = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    ArrayList&lt;ItemInfo&gt; otherWorkspaceItems = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    ArrayList&lt;LauncherAppWidgetInfo&gt; currentAppWidgets = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    ArrayList&lt;LauncherAppWidgetInfo&gt; otherAppWidgets = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (TestProtocol.sDebugTracing) &#123;</span><br><span class="line">        Log.d(TestProtocol.NULL_INT_SET, <span class="string">&quot;bind (1) currentScreenIds: &quot;</span></span><br><span class="line">                + currentScreenIds</span><br><span class="line">                + <span class="string">&quot;, pointer: &quot;</span></span><br><span class="line">                + mCallbacks</span><br><span class="line">                + <span class="string">&quot;, name: &quot;</span></span><br><span class="line">                + mCallbacks.getClass().getName());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 区分是否在当前屏幕 filterCurrentWorkspaceItems()，</span></span><br><span class="line">    <span class="comment">// 通过比较 if (currentScreenIds.contains(info.screenId)) 来确定是否在当前屏幕</span></span><br><span class="line">    filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,</span><br><span class="line">            otherWorkspaceItems);</span><br><span class="line">    <span class="keyword">if</span> (TestProtocol.sDebugTracing) &#123;</span><br><span class="line">        Log.d(TestProtocol.NULL_INT_SET, <span class="string">&quot;bind (2) currentScreenIds: &quot;</span></span><br><span class="line">                + currentScreenIds);</span><br><span class="line">    &#125;</span><br><span class="line">    filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,</span><br><span class="line">            otherAppWidgets);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">InvariantDeviceProfile</span> <span class="variable">idp</span> <span class="operator">=</span> mApp.getInvariantDeviceProfile();</span><br><span class="line">    <span class="comment">// 然后将图标进行整理，将图标从上到下从左到右按顺序排好，</span></span><br><span class="line">    <span class="comment">// 因为图标的显示始终是一个一个依次显示，虽然速度很快，</span></span><br><span class="line">    <span class="comment">// 但是在手机卡顿的时候，难免第一个图标和最后一个图标还是能被人感知。</span></span><br><span class="line">    <span class="comment">// 如果有顺序的显示，用户体验会好很多。</span></span><br><span class="line">    sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);</span><br><span class="line">    sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 告诉 workspace 我们即将开始绑定项目</span></span><br><span class="line">    <span class="comment">// 这里调用了 Launcher 的 startBinding 方法，</span></span><br><span class="line">    <span class="comment">// google Launcher 的习惯先用一个start的方法作为一个实际操作的开始，</span></span><br><span class="line">    <span class="comment">// 这里的 startBinding 会完成 resetLayout 等清空数据的操作</span></span><br><span class="line">    executeCallbacksTask(c -&gt; &#123;</span><br><span class="line">        c.clearPendingBinds();</span><br><span class="line">        c.startBinding();</span><br><span class="line">    &#125;, mUiExecutor);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 而后是核心代码，首先绑定屏幕，传入的参数是 mOrderedScreenIds，参数源于数据库。</span></span><br><span class="line">    executeCallbacksTask(c -&gt; c.bindScreens(mOrderedScreenIds), mUiExecutor);</span><br><span class="line"></span><br><span class="line">    <span class="comment">///以上完成了屏幕的添加，随后就添加桌面的图标和 widget，于是传入了当前显示屏幕的图标和 widget。</span></span><br><span class="line">    <span class="comment">// 这是第一屏幕绑定</span></span><br><span class="line">    bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);</span><br><span class="line">    bindAppWidgets(currentAppWidgets, mUiExecutor);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略部分代码......</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这是其他屏幕绑定</span></span><br><span class="line">    bindWorkspaceItems(otherWorkspaceItems, pendingExecutor);</span><br><span class="line">    bindAppWidgets(otherAppWidgets, pendingExecutor);</span><br><span class="line">    <span class="comment">// 紧接着告诉桌面我们已经绑定完成，</span></span><br><span class="line">    <span class="comment">// 即调用 finishBindingItems ，和之前的start方法形成照应</span></span><br><span class="line">    executeCallbacksTask(c -&gt; c.finishBindingItems(currentScreenIds), pendingExecutor);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略部分代码......</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码最后面的四个绑定操作：</p>
<ul>
<li>c.startBinding()</li>
<li>c.bindScreens()</li>
<li>bindWorkspaceItems()</li>
<li>bindAppWidgets()</li>
</ul>
<p>四个绑定操作中，下面将对： **c.bindScreens()**、 <strong>bindWorkspaceItems()</strong> 这两个展开分析。</p>
<h4 id="4-1-第一个绑定操作"><a href="#4-1-第一个绑定操作" class="headerlink" title="4.1 第一个绑定操作"></a>4.1 第一个绑定操作</h4><p>像 <strong>c.startBinding()</strong> 、 <strong>c.bindScreens()</strong> 这两个直接回调到 <strong>Launcher.java</strong> 中。</p>
<p>这里先看下 <strong>c.bindScreens()</strong> 方法 <strong>Launcher#bindScreens()</strong>:</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/Launcher.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这里要注意点：注意定制的google搜索栏不存于数据库中，其具备不可移动不可删除的特性，而 google 搜索栏在创建时是随着屏幕一同创建的。</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bindScreens</span><span class="params">(IntArray orderedScreenIds)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">firstScreenPosition</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (FeatureFlags.QSB_ON_FIRST_SCREEN &amp;&amp;</span><br><span class="line">            orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) &#123;</span><br><span class="line">        orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);</span><br><span class="line">        orderedScreenIds.add(firstScreenPosition, Workspace.FIRST_SCREEN_ID);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!FeatureFlags.QSB_ON_FIRST_SCREEN &amp;&amp; orderedScreenIds.isEmpty()) &#123;</span><br><span class="line">        <span class="comment">// If there are no screens, we need to have an empty screen</span></span><br><span class="line">        mWorkspace.addExtraEmptyScreens();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//对于绑定屏幕实质是：创建与数据库中屏幕数一致的空屏幕。</span></span><br><span class="line">    <span class="comment">// 该方法里面会一直调到：Workspace#insertNewWorkspaceScreen() 方法，</span></span><br><span class="line">    <span class="comment">// 通过 addview() 添加添加空屏幕</span></span><br><span class="line">    bindAddScreens(orderedScreenIds);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// After we have added all the screens, if the wallpaper was locked to the default state,</span></span><br><span class="line">    <span class="comment">// then notify to indicate that it can be released and a proper wallpaper offset can be</span></span><br><span class="line">    <span class="comment">// computed before the next layout</span></span><br><span class="line">    mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>以上完成了屏幕的添加，随后就添加桌面的图标和 <strong>widget</strong>，于是传入了当前显示屏幕的图标和 <strong>widget</strong>。</p>
<h4 id="4-2-第二个绑定操作"><a href="#4-2-第二个绑定操作" class="headerlink" title="4.2 第二个绑定操作"></a>4.2 第二个绑定操作</h4><p>接着看第二个绑定操作 <strong>bindWorkspaceItems()</strong> ，绑定图标是回调 <strong>Launcher.java</strong> 的对应方法，且绑定时按照不同 item 类型进行不同的绘制。<br>看 <strong>Launcher#bindItems()</strong>:</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/Launcher.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bindItems</span><span class="params">(</span></span><br><span class="line"><span class="params">        <span class="keyword">final</span> List&lt;ItemInfo&gt; items,</span></span><br><span class="line"><span class="params">        <span class="keyword">final</span> <span class="type">boolean</span> forceAnimateIcons,</span></span><br><span class="line"><span class="params">        <span class="keyword">final</span> <span class="type">boolean</span> focusFirstItemForAccessibility)</span> &#123;</span><br><span class="line">    <span class="comment">// Get the list of added items and intersect them with the set of items here</span></span><br><span class="line">    <span class="keyword">final</span> Collection&lt;Animator&gt; bounceAnims = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">canAnimatePageChange</span> <span class="operator">=</span> canAnimatePageChange();</span><br><span class="line">    Workspace&lt;?&gt; workspace = mWorkspace;</span><br><span class="line">    <span class="type">int</span> <span class="variable">newItemsScreenId</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> items.size();</span><br><span class="line">    <span class="type">View</span> <span class="variable">newView</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; end; i++) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">ItemInfo</span> <span class="variable">item</span> <span class="operator">=</span> items.get(i);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 首先进行一个简单判断，如果当前图标是放在快捷栏，而当前手机是没有快捷栏的，则不进行这个图标显示。</span></span><br><span class="line">        <span class="keyword">if</span> (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &amp;&amp;</span><br><span class="line">                mHotseat == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">final</span> View view;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">switch</span> (item.itemType) &#123;</span><br><span class="line">            <span class="comment">// 图标有所细分，单个图标的统一为一类，使用createShortcut() 来创建。</span></span><br><span class="line">            <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:</span><br><span class="line">            <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:</span><br><span class="line">            <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: &#123;</span><br><span class="line">                <span class="type">WorkspaceItemInfo</span> <span class="variable">info</span> <span class="operator">=</span> (WorkspaceItemInfo) item;</span><br><span class="line">                <span class="comment">// *********1、重点关注 ********</span></span><br><span class="line">                view = createShortcut(info);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_FOLDER: &#123;</span><br><span class="line">                <span class="comment">// *********2、重点关注 ********</span></span><br><span class="line">                view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, <span class="built_in">this</span>,</span><br><span class="line">                        (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),</span><br><span class="line">                        (FolderInfo) item);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:</span><br><span class="line">            <span class="keyword">case</span> LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: &#123;</span><br><span class="line">                <span class="comment">// *********3、重点关注 ********</span></span><br><span class="line">                view = inflateAppWidget((LauncherAppWidgetInfo) item);</span><br><span class="line">                <span class="keyword">if</span> (view == <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">default</span>:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Invalid Item Type&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 省略部分代码......</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码有三个需要重点关注的位置： <strong>createShortcut(info)</strong> 、 <strong>inflateFolderAndIcon()</strong> 、 <strong>inflateAppWidget()</strong> 。</p>
<h4 id="4-2-1-第一个关注点-createShortcut-info"><a href="#4-2-1-第一个关注点-createShortcut-info" class="headerlink" title="4.2.1 第一个关注点 createShortcut(info)"></a>4.2.1 第一个关注点 createShortcut(info)</h4><p>第一个重点关注<strong>Launcher#createShortcut()</strong>:<br><strong>packages/apps/Launcher3/src/com/android/launcher3/Launcher.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建表示从指定资源扩展的快捷方式的视图。</span></span><br><span class="line"><span class="keyword">public</span> View <span class="title function_">createShortcut</span><span class="params">(ViewGroup parent, WorkspaceItemInfo info)</span> &#123;</span><br><span class="line">    <span class="type">BubbleTextView</span> <span class="variable">favorite</span> <span class="operator">=</span> (BubbleTextView) LayoutInflater.from(parent.getContext())</span><br><span class="line">            .inflate(R.layout.app_icon, parent, <span class="literal">false</span>);</span><br><span class="line">    favorite.applyFromWorkspaceItem(info);</span><br><span class="line">    favorite.setOnClickListener(ItemClickHandler.INSTANCE);</span><br><span class="line">    favorite.setOnFocusChangeListener(mFocusHandler);</span><br><span class="line">    <span class="keyword">return</span> favorite;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里面又有三个关键方法，非常值得关注。<br>第一个 <strong>BubbleTextView#applyFromWorkspaceItem()</strong> :</p>
<p><strong>packages/apps/Launcher3/src/com/android/launcher3/BubbleTextView.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@UiThread</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">applyFromWorkspaceItem</span><span class="params">(WorkspaceItemInfo info, <span class="type">boolean</span> promiseStateChanged)</span> &#123;</span><br><span class="line">    <span class="comment">// 设置应用图标、应用名称</span></span><br><span class="line">    applyIconAndLabel(info); </span><br><span class="line">    setItemInfo(info);</span><br><span class="line">    <span class="comment">// 如果此应用程序正在安装，进度条将随着安装进度更新</span></span><br><span class="line">    applyLoadingState(promiseStateChanged); </span><br><span class="line">    <span class="comment">// 设置、删除绿点；因为首次安装的应用有个绿点</span></span><br><span class="line">    applyDotState(info, <span class="literal">false</span> <span class="comment">/* animate */</span>);</span><br><span class="line">    <span class="comment">// 设置下载状态内容说明；例如：下载中、暂停</span></span><br><span class="line">    setDownloadStateContentDescription(info, info.getProgressLevel());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>第二个 <strong>favorite.setOnClickListener(ItemClickHandler.INSTANCE)</strong> 这里传入的是 <strong>ItemClickHandler</strong> 中的 <strong>OnClickListener</strong> 。设置图标点击事件，看 <strong>ItemClickHandler#onClick()</strong>:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">onClick</span><span class="params">(View v)</span> &#123;</span><br><span class="line">    <span class="comment">// 确保在所有应用程序启动时或在视图分离后</span></span><br><span class="line">    <span class="comment">// （如果视图在触摸中途被移除，可能发生这种情况），恶意点击不会通过。</span></span><br><span class="line">    <span class="keyword">if</span> (v.getWindowToken() == <span class="literal">null</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">Launcher</span> <span class="variable">launcher</span> <span class="operator">=</span> Launcher.getLauncher(v.getContext());</span><br><span class="line">    <span class="keyword">if</span> (!launcher.getWorkspace().isFinishedSwitchingState()) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">Object</span> <span class="variable">tag</span> <span class="operator">=</span> v.getTag();</span><br><span class="line">    <span class="keyword">if</span> (tag <span class="keyword">instanceof</span> WorkspaceItemInfo) &#123;</span><br><span class="line">        <span class="comment">// 应用程序快捷方式单击的事件处理。也是调用到：startAppShortcutOrInfoActivity() 方法。</span></span><br><span class="line">        onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (tag <span class="keyword">instanceof</span> FolderInfo) &#123;</span><br><span class="line">        <span class="keyword">if</span> (v <span class="keyword">instanceof</span> FolderIcon) &#123;</span><br><span class="line">            <span class="comment">// 单击文件夹图标的事件处理程序</span></span><br><span class="line">            onClickFolderIcon(v);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (tag <span class="keyword">instanceof</span> AppInfo) &#123;</span><br><span class="line">        <span class="comment">// 启动应用程序快捷方式或信息活动</span></span><br><span class="line">        startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (tag <span class="keyword">instanceof</span> LauncherAppWidgetInfo) &#123;</span><br><span class="line">        <span class="keyword">if</span> (v <span class="keyword">instanceof</span> PendingAppWidgetHostView) &#123;</span><br><span class="line">            <span class="comment">// 尚未完全恢复的应用小部件视图的事件处理程序</span></span><br><span class="line">            onClickPendingWidget((PendingAppWidgetHostView) v, launcher);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (tag <span class="keyword">instanceof</span> SearchActionItemInfo) &#123;</span><br><span class="line">        <span class="comment">// SearchActionItemInfo 点击的事件处理程序</span></span><br><span class="line">        onClickSearchAction(launcher, (SearchActionItemInfo) tag);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>第三个 favorite.setOnFocusChangeListener(mFocusHandler)： 外接键盘选择功能。被focus的图标会有灰色背景显示被选中。此外还有一定动画效果，都在focus类里。<br>第一个关注 Launcher#createShortcut() 方法就到此结束。</p>
<h4 id="4-2-2-第二个关注点-inflateFolderAndIcon"><a href="#4-2-2-第二个关注点-inflateFolderAndIcon" class="headerlink" title="4.2.2 第二个关注点 inflateFolderAndIcon()"></a>4.2.2 第二个关注点 inflateFolderAndIcon()</h4><p>接下来看第二个关注的方法 FolderIcon#inflateFolderAndIcon():<br><strong>packages/apps/Launcher3/src/com/android/launcher3/folder/FolderIcon.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Context</span> &amp; ActivityContext&gt; FolderIcon <span class="title function_">inflateFolderAndIcon</span><span class="params">(<span class="type">int</span> resId,</span></span><br><span class="line"><span class="params">        T activityContext, ViewGroup group, FolderInfo folderInfo)</span> &#123;</span><br><span class="line">    <span class="comment">// folder 图标的生成是一个名叫 fromXml() 的方法</span></span><br><span class="line">    <span class="type">Folder</span> <span class="variable">folder</span> <span class="operator">=</span> Folder.fromXml(activityContext);</span><br><span class="line">    <span class="comment">// FolderIcon是文件夹的图标，Folder是打开时的文件夹。</span></span><br><span class="line">    <span class="type">FolderIcon</span> <span class="variable">icon</span> <span class="operator">=</span> inflateIcon(resId, activityContext, group, folderInfo);</span><br><span class="line">    folder.setFolderIcon(icon);</span><br><span class="line">    folder.bind(folderInfo);</span><br><span class="line">    icon.setFolder(folder);</span><br><span class="line">    <span class="keyword">return</span> icon;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>这里注意：FolderIcon是文件夹的图标，Folder 是打开时的文件夹 (不是里面的应用图标)。</p>
</blockquote>
<p>到这里可以发现应用图标是 <strong>textview</strong> 而文件夹是 <strong>FrameLayout</strong>。后面就不过多介绍了，和应用一样生成名字，大小，click，focus 等。</p>
<h4 id="4-2-3-第三个关注点-inflateAppWidget"><a href="#4-2-3-第三个关注点-inflateAppWidget" class="headerlink" title="4.2.3 第三个关注点 inflateAppWidget()"></a>4.2.3 第三个关注点 inflateAppWidget()</h4><p>最后看第三个关注点 <strong>Launcher#inflateAppWidget()</strong> ，看里面的 <strong>AppWidgetHost.createView()</strong> :</p>
<p><strong>frameworks/base/core/java/android/appwidget/AppWidgetHost.java</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> AppWidgetHostView <span class="title function_">createView</span><span class="params">(Context context, <span class="type">int</span> appWidgetId,</span></span><br><span class="line"><span class="params">        AppWidgetProviderInfo appWidget)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (sService == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// AppWidgetHostView 继承至 FrameLayout</span></span><br><span class="line">    <span class="type">AppWidgetHostView</span> <span class="variable">view</span> <span class="operator">=</span> onCreateView(context, appWidgetId, appWidget);</span><br><span class="line">    view.setInteractionHandler(mInteractionHandler);</span><br><span class="line">    <span class="comment">// 设置此视图将显示的AppWidget</span></span><br><span class="line">    view.setAppWidget(appWidgetId, appWidget);</span><br><span class="line">    <span class="keyword">synchronized</span> (mViews) &#123;</span><br><span class="line">        mViews.put(appWidgetId, view);</span><br><span class="line">    &#125;</span><br><span class="line">    RemoteViews views;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemoteException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;system server dead?&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">    view.updateAppWidget(views);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> view;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>以上 <strong>bindItems</strong> 就是按照分类把每种类型的桌面的 view 一个一个的创造出来。完成了当前屏幕的绘制，而后进行其他屏幕的 view 绘制。都在同一个方法调用绑定 <strong>BaseLoaderResults#bind()</strong> ，只是传入的 list 为 <strong>otherWorkspaceItems</strong> 和 <strong>otherAppWidgets</strong> 。</p>
<p>至此 <strong>Workspace</strong> 的数据加载与绑定结束。这里当我注释掉 <strong>loadAllApps()</strong> 后，当前屏幕是有应用图标的(我这是：相册、Google助理、Play商店、最下面电话、短信等图标都有) ，但上滑界面进入到 AllApps 界面时，没有任何图标。</p>

      
      <blockquote>
        
        <strong>本文链接：</strong><br><a href="http://longzhiye.top/2024/02/17/2024-02-17/">http://longzhiye.top/2024/02/17/2024-02-17/</a>
      </blockquote>
    </div>
    <footer class="mdui-card-actions">
      
        <a class="mdui-ripple article_categories-link" href="/categories/%E6%8A%80%E6%9C%AF/">技术</a>
      
      
        <a class="mdui-ripple article_tags-link" href="/tags/Android/" rel="tag">Android</a><a class="mdui-ripple article_tags-link" href="/tags/Launcher/" rel="tag">Launcher</a><a class="mdui-ripple article_tags-link" href="/tags/framework/" rel="tag">framework</a>
      
    </footer>
    
  </article>
  
<script src="//cdn.bootcdn.net/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdn.bootcdn.net/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js"></script>

  <script>$("#main article .mdui-card-content img.fancybox").on("click",function(e){$.fancybox.open({src:$(this).attr("src")});});</script>


  <nav id="paginator">
    
      <a rel="prev" class="extend prev" href="/2024/02/24/2024-02-24/">
        <button aria-label="prev" class="mdui-btn mdui-btn-raised mdui-btn-icon mdui-color-theme-accent mdui-ripple"><i class="mdui-icon material-icons">arrow_back</i></button>
        <span class="mdui-p-x-3" mdui-tooltip="{content: 'Android 14 Handler 源码解析'}">上一篇</span>
      </a>
    
    <div class="spacer"></div>
    
      <a rel="next" class="extend next" href="/2024/02/16/2024-02-16/">
        <span class="mdui-p-x-3" mdui-tooltip="{content: 'Android 14 Launcher 数据加载分析（二）'}">下一篇</span>
        <button aria-label="next" class="mdui-btn mdui-btn-raised mdui-btn-icon mdui-color-theme-accent mdui-ripple"><i class="mdui-icon material-icons">arrow_forward</i></button>
      </a>
    
  </nav>





</main>
  <footer id="footer" class="mdui-m-t-5 mdui-p-y-3 mdui-color-theme">
  <div class="mdui-p-y-0 mdui-text-center">
    
    
    
    
    
    
    
      <a href="http://github.com/longzhiye" target="_blank" class="mdui-btn mdui-btn-icon mdui-text-color-theme-a100"><i class="mdui-icon iconfont">&#xe7ab;</i></a>
    
    
    
      <a href="https://www.zhihu.com/people/long-zhi-xie-61" target="_blank" class="mdui-btn mdui-btn-icon mdui-text-color-theme-a100"><i class="mdui-icon iconfont">&#xe6c0;</i></a>
    
    
    
      <a href="tencent://tencent:////message/?uin=951898105&Menu=yes" target="_blank" class="mdui-btn mdui-btn-icon mdui-text-color-theme-a100"><i class="mdui-icon iconfont">&#xe651;</i></a>
    
    
  </div>
  <div class="mdui-p-y-1 mdui-text-center">
    Copyright &copy; 2022 - 2025 龙之叶<br>
    Powered by <a href="https://hexo.io/" target="_blank" class="mdui-text-color-theme-accent">Hexo</a>
    <br>
      <span id="busuanzi_container_site_pv" style="display: none;"><i class="iconfont">&#xe7fd;</i> <span id="busuanzi_value_site_pv"></span></span> &nbsp;&nbsp;
      <span id="busuanzi_container_site_uv" style="display: none;"><i class="iconfont">&#xe601;</i> <span id="busuanzi_value_site_uv"></span></span>
    
  </div>
</footer>

  <button id="gotop" class="mdui-fab mdui-fab-fixed mdui-fab-hide mdui-color-theme-accent mdui-ripple"><i class="mdui-icon material-icons">arrow_upward</i></button>
  <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
  
<script src="/js/mdui.js"></script>
<script src="/js/script.js"></script>

  
<script src="/custom.js"></script>

</body>
</html>
